diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d0647ee5..9e106f884 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,167 +1,167 @@ project(kdepimlibs) # where to look first for cmake modules. This line must be the first one or cmake will use the system's FindFoo.cmake set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") ############### Build Options ############### option(KDEPIM_ONLY_KLEO "Only build the libraries needed by Kleopatra." FALSE) ############### The kdepimlibs version (used e.g. in KdepimLibsConfig.cmake) ############### set(KDEPIMLIBS_VERSION_MAJOR 4) set(KDEPIMLIBS_VERSION_MINOR 2) -set(KDEPIMLIBS_VERSION_PATCH 88) +set(KDEPIMLIBS_VERSION_PATCH 90) set(KDEPIMLIBS_VERSION ${KDEPIMLIBS_VERSION_MAJOR}.${KDEPIMLIBS_VERSION_MINOR}.${KDEPIMLIBS_VERSION_PATCH}) ############### search packages used by KDE ############### -find_package(KDE4 4.2.85 REQUIRED) # see SVN rev. 952875/954493 +find_package(KDE4 4.2.90 REQUIRED) # see SVN rev. 952875/954493 include(KDE4Defaults) include(MacroLibrary) ############### Needed commands before building anything ############### add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) ############### Find the stuff we need ############### set(Boost_MINIMUM_VERSION "1.33.1") find_package(Boost) macro_log_feature(Boost_FOUND "boost" "Boost C++ Libraries" "http://www.boost.org" TRUE "" "Required by several critical KDEPIM apps.") #FindGpgme.cmake already handles the log message but we must ensure it is required. find_package(Gpgme REQUIRED) # configure macros if (GPGME_FOUND) include (gpgme++/ConfigureChecks.cmake) endif (GPGME_FOUND) if (NOT KDEPIM_ONLY_KLEO) - find_package(Akonadi 1.1.85) + find_package(Akonadi 1.1.90) macro_log_feature(Akonadi_FOUND "Akonadi" "Akonadi server (from kdesupport)" "http://pim.kde.org/akonadi" TRUE "" "Akonadi is required to build KdepimLibs.") find_package(Sasl2) macro_log_feature(SASL2_FOUND "cyrus-sasl" "Cyrus SASL API" "http://asg.web.cmu.edu/sasl/sasl-library.html" TRUE "" "Required to support authentication of logins in the IMAP and Sieve kioslaves.") include (ConfigureChecks.cmake) endif (NOT KDEPIM_ONLY_KLEO) ############### Now, we add the KDEPIMLibs components ############### # These targets will always be built add_subdirectory(cmake) add_subdirectory(gpgme++) add_subdirectory(qgpgme) if (NOT KDEPIM_ONLY_KLEO) add_subdirectory(akonadi) add_subdirectory(kabc) add_subdirectory(kblog) add_subdirectory(kcal) add_subdirectory(kholidays) add_subdirectory(kimap) add_subdirectory(kioslave) add_subdirectory(kldap) add_subdirectory(kmime) add_subdirectory(kpimidentities) add_subdirectory(kpimutils) add_subdirectory(kpimtextedit) add_subdirectory(kresources) add_subdirectory(ktnef) add_subdirectory(kxmlrpcclient) add_subdirectory(mailtransport) add_subdirectory(microblog) add_subdirectory(outboxinterface) add_subdirectory(syndication) # Build the CamelCase headers add_subdirectory(includes) endif (NOT KDEPIM_ONLY_KLEO) # doc must be a subdir of kdepimlibs macro_optional_add_subdirectory(doc) # All done, let's display what we found... macro_display_feature_log() # now create the KdepimLibsConfig.cmake file, which will be loaded by # kdelibs/cmake/modules/FindKdepimLibs.cmake and which has to contain all information # about the installed kdepimlibs anybody would like to have. Alex # we need the absolute directories where stuff will be installed too # but since the variables which contain the destinations can be relative # or absolute paths, we need this macro to make them all absoulte, Alex macro(MAKE_INSTALL_PATH_ABSOLUTE out in) if (IS_ABSOLUTE "${in}") # IS_ABSOLUTE is new since cmake 2.4.8 set(${out} "${in}") else (IS_ABSOLUTE "${in}") set(${out} "\${KDEPIMLIBS_INSTALL_DIR}/${in}") endif (IS_ABSOLUTE "${in}") endmacro(MAKE_INSTALL_PATH_ABSOLUTE out in) # all the following variables are put into KdepimLibsConfig.cmake, so # they are usable by projects using kdepimlibs. Alex make_install_path_absolute(KDEPIMLIBS_DATA_DIR ${DATA_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_DBUS_INTERFACES_DIR ${DBUS_INTERFACES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_DBUS_SERVICES_DIR ${DBUS_SERVICES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_INCLUDE_DIR ${INCLUDE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LIB_DIR ${LIB_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_BIN_DIR ${BIN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LIBEXEC_DIR ${LIBEXEC_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SBIN_DIR ${SBIN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_HTML_DIR ${HTML_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_CONFIG_DIR ${CONFIG_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_ICON_DIR ${ICON_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_KCFG_DIR ${KCFG_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LOCALE_DIR ${LOCALE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_MIME_DIR ${MIME_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SOUND_DIR ${SOUND_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_TEMPLATES_DIR ${TEMPLATES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_WALLPAPER_DIR ${WALLPAPER_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_KCONF_UPDATE_DIR ${KCONF_UPDATE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_AUTOSTART_DIR ${AUTOSTART_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_XDG_APPS_DIR ${XDG_APPS_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_XDG_DIRECTORY_DIR ${XDG_DIRECTORY_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SYSCONF_DIR ${SYSCONF_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_MAN_DIR ${MAN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_INFO_DIR ${INFO_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SERVICES_DIR ${SERVICES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SERVICETYPES_DIR ${SERVICETYPES_INSTALL_DIR}) # Used in configure_file() and install(EXPORT) set(KDEPIMLIBS_TARGET_PREFIX KDEPIMLibs__) # this file is installed and contains all necessary information about the installed kdepimlibs, it also loads the file with the exported targets configure_file(KdepimLibsConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfig.cmake" @ONLY) # this file will be installed too and will be used by cmake when searching for the Config.cmake file to check the version of kdepimlibs, Alex macro_write_basic_cmake_version_file(${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfigVersion.cmake ${KDEPIMLIBS_VERSION_MAJOR} ${KDEPIMLIBS_VERSION_MINOR} ${KDEPIMLIBS_VERSION_PATCH}) set(_KdepimLibsConfig_INSTALL_DIR ${LIB_INSTALL_DIR}/KdepimLibs/cmake) # places where find_package() looks for FooConfig.cmake files: # CMake >= 2.6.0 looks in lib/Foo*/cmake/, CMake >= 2.6.3 also looks in # lib/cmake/Foo*/, which packagers prefer. So they can set the KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR # option to have kdepimlibs install its Config file there. Alex if(KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR) set(_KdepimLibsConfig_INSTALL_DIR ${LIB_INSTALL_DIR}/cmake/KdepimLibs) endif(KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfig.cmake DESTINATION ${_KdepimLibsConfig_INSTALL_DIR} ) # Install the file with the exported targets, use ${KDEPIMLIBS_TARGET_PREFIX} as prefix for the names of these targets, Alex install(EXPORT kdepimlibsLibraryTargets NAMESPACE ${KDEPIMLIBS_TARGET_PREFIX} DESTINATION ${_KdepimLibsConfig_INSTALL_DIR} FILE KDEPimLibsLibraryTargetsWithPrefix.cmake ) # Install a KDEPimLibsDependencies.cmake so people using kdepimlibs 4.2 with kdelibs < 4.2 get a useful error message, Alex file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/KDEPimLibsDependencies.cmake "\n message(FATAL_ERROR \"For using this version of kdepimlibs (${KDEPIMLIBS_VERSION}) you need a newer version of kdelibs, please update.\")\n") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KDEPimLibsDependencies.cmake DESTINATION ${DATA_INSTALL_DIR}/cmake/modules) diff --git a/akonadi/agentbase.cpp b/akonadi/agentbase.cpp index 73ed4cd34..49f4a8bdd 100644 --- a/akonadi/agentbase.cpp +++ b/akonadi/agentbase.cpp @@ -1,525 +1,552 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause Copyright (c) 2007 Bruno Virlet Copyright (c) 2008 Kevin Krammer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "agentbase.h" #include "agentbase_p.h" #include "controladaptor.h" #include "statusadaptor.h" #include "monitor_p.h" #include "xdgbasedirs_p.h" #include "session.h" #include "session_p.h" #include "changerecorder.h" #include "itemfetchjob.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; static AgentBase *sAgentBase = 0; AgentBase::Observer::Observer() { } AgentBase::Observer::~Observer() { } void AgentBase::Observer::itemAdded( const Item &item, const Collection &collection ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( item ); Q_UNUSED( collection ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::itemChanged( const Item &item, const QSet &partIdentifiers ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( item ); Q_UNUSED( partIdentifiers ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::itemRemoved( const Item &item ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( item ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( collection ); Q_UNUSED( parent ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::collectionChanged( const Collection &collection ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( collection ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } void AgentBase::Observer::collectionRemoved( const Collection &collection ) { kDebug() << "sAgentBase=" << (void*) sAgentBase << "this=" << (void*) this; Q_UNUSED( collection ); if ( sAgentBase != 0 ) sAgentBase->d_ptr->changeProcessed(); } //@cond PRIVATE AgentBasePrivate::AgentBasePrivate( AgentBase *parent ) : q_ptr( parent ), mStatusCode( AgentBase::Idle ), mProgress( 0 ), mNeedsNetwork( false ), mOnline( false ), mSettings( 0 ), mObserver( 0 ) { } AgentBasePrivate::~AgentBasePrivate() { mMonitor->setConfig( 0 ); delete mSettings; } void AgentBasePrivate::init() { Q_Q( AgentBase ); /** * Create a default session for this process. */ SessionPrivate::createDefaultSession( mId.toLatin1() ); mTracer = new org::freedesktop::Akonadi::Tracer( QLatin1String( "org.freedesktop.Akonadi" ), QLatin1String( "/tracing" ), QDBusConnection::sessionBus(), q ); new ControlAdaptor( q ); new StatusAdaptor( q ); if ( !QDBusConnection::sessionBus().registerObject( QLatin1String( "/" ), q, QDBusConnection::ExportAdaptors ) ) q->error( QString::fromLatin1( "Unable to register object at dbus: %1" ).arg( QDBusConnection::sessionBus().lastError().message() ) ); mSettings = new QSettings( QString::fromLatin1( "%1/agent_config_%2" ).arg( XdgBaseDirs::saveDir( "config", QLatin1String( "akonadi" ) ), mId ), QSettings::IniFormat ); mMonitor = new ChangeRecorder( q ); mMonitor->ignoreSession( Session::defaultSession() ); mMonitor->itemFetchScope().setCacheOnly( true ); mMonitor->setConfig( mSettings ); mOnline = mSettings->value( QLatin1String( "Agent/Online" ), true ).toBool(); connect( mMonitor, SIGNAL( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ), SLOT( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ) ); connect( mMonitor, SIGNAL( itemChanged( const Akonadi::Item&, const QSet& ) ), SLOT( itemChanged( const Akonadi::Item&, const QSet& ) ) ); + connect( mMonitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), + SLOT(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)) ); connect( mMonitor, SIGNAL( itemRemoved( const Akonadi::Item& ) ), SLOT( itemRemoved( const Akonadi::Item& ) ) ); connect( mMonitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), SLOT(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); connect( mMonitor, SIGNAL( collectionChanged( const Akonadi::Collection& ) ), SLOT( collectionChanged( const Akonadi::Collection& ) ) ); connect( mMonitor, SIGNAL( collectionRemoved( const Akonadi::Collection& ) ), SLOT( collectionRemoved( const Akonadi::Collection& ) ) ); connect( q, SIGNAL( status( int, QString ) ), q, SLOT( slotStatus( int, QString ) ) ); connect( q, SIGNAL( percent( int ) ), q, SLOT( slotPercent( int ) ) ); connect( q, SIGNAL( warning( QString ) ), q, SLOT( slotWarning( QString ) ) ); connect( q, SIGNAL( error( QString ) ), q, SLOT( slotError( QString ) ) ); // Use reference counting to allow agents to finish internal jobs when the // agent is stopped. KGlobal::ref(); KGlobal::setAllowQuit(true); QTimer::singleShot( 0, q, SLOT( delayedInit() ) ); } void AgentBasePrivate::delayedInit() { Q_Q( AgentBase ); if ( !QDBusConnection::sessionBus().registerService( QLatin1String( "org.freedesktop.Akonadi.Agent." ) + mId ) ) kFatal() << "Unable to register service at dbus:" << QDBusConnection::sessionBus().lastError().message(); q->setOnline( mOnline ); } void AgentBasePrivate::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->itemAdded( item, collection ); } void AgentBasePrivate::itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->itemChanged( item, partIdentifiers ); } +void AgentBasePrivate::itemMoved( const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest ) +{ + kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; + if ( mObserver ) { + // inter-resource moves, requires we know which resources the source and destination are in though + if ( !source.resource().isEmpty() && !dest.resource().isEmpty() ) { + if ( source.resource() != dest.resource() ) { + if ( source.resource() == q_ptr->identifier() ) // moved away from us + mObserver->itemRemoved( item ); + else if ( dest.resource() == q_ptr->identifier() ) // moved to us + mObserver->itemAdded( item, dest ); + else // not for us, not sure if we should get here at all + changeProcessed(); + return; + } + } + // either incomplete information or intra-resource move + // ### we cannot just call itemRemoved here as this will already trigger changeProcessed() + // so, just itemAdded() is good enough as no resource can have implemented intra-resource moves anyway + // without using Observer2 + mObserver->itemAdded( item, dest ); + // mObserver->itemRemoved( item ); + } +} + void AgentBasePrivate::itemRemoved( const Akonadi::Item &item ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->itemRemoved( item ); } void AgentBasePrivate::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->collectionAdded( collection, parent ); } void AgentBasePrivate::collectionChanged( const Akonadi::Collection &collection ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->collectionChanged( collection ); } void AgentBasePrivate::collectionRemoved( const Akonadi::Collection &collection ) { kDebug() << "mObserver=" << (void*) mObserver << "this=" << (void*) this; if ( mObserver != 0 ) mObserver->collectionRemoved( collection ); } void AgentBasePrivate::changeProcessed() { mMonitor->changeProcessed(); QTimer::singleShot( 0, mMonitor, SLOT( replayNext() ) ); } void AgentBasePrivate::slotStatus( int status, const QString &message ) { mStatusMessage = message; mStatusCode = 0; switch ( status ) { case AgentBase::Idle: if ( mStatusMessage.isEmpty() ) mStatusMessage = defaultReadyMessage(); mStatusCode = 0; break; case AgentBase::Running: if ( mStatusMessage.isEmpty() ) mStatusMessage = defaultSyncingMessage(); mStatusCode = 1; break; case AgentBase::Broken: if ( mStatusMessage.isEmpty() ) mStatusMessage = defaultErrorMessage(); mStatusCode = 2; break; default: Q_ASSERT( !"Unknown status passed" ); break; } } void AgentBasePrivate::slotPercent( int progress ) { mProgress = progress; } void AgentBasePrivate::slotWarning( const QString& message ) { mTracer->warning( QString::fromLatin1( "AgentBase(%1)" ).arg( mId ), message ); } void AgentBasePrivate::slotError( const QString& message ) { mTracer->error( QString::fromLatin1( "AgentBase(%1)" ).arg( mId ), message ); } void AgentBasePrivate::slotNetworkStatusChange( Solid::Networking::Status stat ) { Q_Q( AgentBase ); q->setOnline( stat == Solid::Networking::Connected ); } AgentBase::AgentBase( const QString & id ) : d_ptr( new AgentBasePrivate( this ) ) { sAgentBase = this; d_ptr->mId = id; d_ptr->init(); if ( KApplication::kApplication() ) KApplication::kApplication()->disableSessionManagement(); } AgentBase::AgentBase( AgentBasePrivate* d, const QString &id ) : d_ptr( d ) { sAgentBase = this; d_ptr->mId = id; d_ptr->init(); } AgentBase::~AgentBase() { delete d_ptr; } static char* sAgentAppName = 0; QString AgentBase::parseArguments( int argc, char **argv ) { QString identifier; if ( argc < 3 ) { kDebug( 5250 ) << "Not enough arguments passed..."; exit( 1 ); } for ( int i = 1; i < argc - 1; ++i ) { if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) identifier = QLatin1String( argv[ i + 1 ] ); } if ( identifier.isEmpty() ) { kDebug( 5250 ) << "Identifier argument missing"; exit( 1 ); } sAgentAppName = qstrdup( identifier.toLatin1().constData() ); KCmdLineArgs::init( argc, argv, sAgentAppName, 0, ki18n("Akonadi Agent"),"0.1" , ki18n("Akonadi Agent") ); KCmdLineOptions options; options.add("identifier ", ki18n("Agent identifier")); KCmdLineArgs::addCmdLineOptions( options ); return identifier; } // @endcond int AgentBase::init( AgentBase *r ) { QApplication::setQuitOnLastWindowClosed( false ); KGlobal::locale()->insertCatalog( QLatin1String("libakonadi") ); int rv = kapp->exec(); delete r; delete[] sAgentAppName; return rv; } int AgentBase::status() const { Q_D( const AgentBase ); return d->mStatusCode; } QString AgentBase::statusMessage() const { Q_D( const AgentBase ); return d->mStatusMessage; } int AgentBase::progress() const { Q_D( const AgentBase ); return d->mProgress; } QString AgentBase::progressMessage() const { Q_D( const AgentBase ); return d->mProgressMessage; } bool AgentBase::isOnline() const { Q_D( const AgentBase ); return d->mOnline; } void AgentBase::setNeedsNetwork( bool needsNetwork ) { Q_D( AgentBase ); d->mNeedsNetwork = needsNetwork; if ( d->mNeedsNetwork ) { connect( Solid::Networking::notifier() , SIGNAL( statusChanged( Solid::Networking::Status ) ) , this, SLOT( slotNetworkStatusChange( Solid::Networking::Status ) ) ); } else { disconnect( Solid::Networking::notifier(), 0, 0, 0 ); setOnline( true ); } } void AgentBase::setOnline( bool state ) { Q_D( AgentBase ); d->mOnline = state; d->mSettings->setValue( QLatin1String( "Agent/Online" ), state ); doSetOnline( state ); emit onlineChanged( state ); } void AgentBase::doSetOnline( bool online ) { Q_UNUSED( online ); } void AgentBase::configure( WId windowId ) { Q_UNUSED( windowId ); } #ifdef Q_OS_WIN //krazy:exclude=cpp void AgentBase::configure( qlonglong windowId ) { configure( reinterpret_cast( windowId ) ); } #endif WId AgentBase::winIdForDialogs() const { bool registered = QDBusConnection::sessionBus().interface()->isServiceRegistered( QLatin1String("org.freedesktop.akonaditray") ); if ( !registered ) return 0; QDBusInterface dbus( QLatin1String("org.freedesktop.akonaditray"), QLatin1String("/Actions"), QLatin1String("org.freedesktop.Akonadi.Tray") ); QDBusMessage reply = dbus.call( QLatin1String("getWinId") ); if ( reply.type() == QDBusMessage::ErrorMessage ) return 0; WId winid = (WId)reply.arguments().at( 0 ).toLongLong(); return winid; } void AgentBase::quit() { Q_D( AgentBase ); aboutToQuit(); if ( d->mSettings ) { d->mMonitor->setConfig( 0 ); d->mSettings->sync(); } KGlobal::deref(); } void AgentBase::aboutToQuit() { } void AgentBase::cleanup() { Q_D( AgentBase ); aboutToQuit(); const QString fileName = d->mSettings->fileName(); /* * First destroy the settings object... */ d->mMonitor->setConfig( 0 ); delete d->mSettings; d->mSettings = 0; /* * ... then remove the file from hd. */ QFile::remove( fileName ); /* * ... and also remove the agent configuration file if there is one. */ QString configFile = KStandardDirs::locateLocal( "config", KGlobal::config()->name() ); QFile::remove( configFile ); KGlobal::deref(); } void AgentBase::registerObserver( Observer *observer ) { kDebug() << "observer=" << (void*) observer << "this=" << (void*) this; d_ptr->mObserver = observer; } QString AgentBase::identifier() const { return d_ptr->mId; } void AgentBase::changeProcessed() { Q_D( AgentBase ); d->changeProcessed(); } ChangeRecorder * AgentBase::changeRecorder() const { return d_ptr->mMonitor; } void AgentBase::reconfigure() { emit reloadConfiguration(); } #include "agentbase.moc" #include "agentbase_p.moc" diff --git a/akonadi/agentbase_p.h b/akonadi/agentbase_p.h index 28237df88..8b864f72a 100644 --- a/akonadi/agentbase_p.h +++ b/akonadi/agentbase_p.h @@ -1,109 +1,106 @@ /* Copyright (c) 2007 Volker Krause Copyright (c) 2008 Kevin Krammer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_AGENTBASE_P_H #define AKONADI_AGENTBASE_P_H #include "agentbase.h" #include "tracerinterface.h" #include #include class QSettings; namespace Akonadi { /** * @internal */ class AgentBasePrivate : public QObject { Q_OBJECT public: AgentBasePrivate( AgentBase *parent ); virtual ~AgentBasePrivate(); void init(); virtual void delayedInit(); void slotStatus( int status, const QString &message ); void slotPercent( int progress ); void slotWarning( const QString& message ); void slotError( const QString& message ); void slotNetworkStatusChange( Solid::Networking::Status ); virtual void changeProcessed(); QString defaultReadyMessage() const { if ( mOnline ) return i18nc( "@info:status, application ready for work", "Ready" ); return i18nc( "@info:status", "Offline" ); } QString defaultSyncingMessage() const { return i18nc( "@info:status", "Syncing..." ); } QString defaultErrorMessage() const { return i18nc( "@info:status", "Error." ); } AgentBase *q_ptr; Q_DECLARE_PUBLIC( AgentBase ) QString mId; int mStatusCode; QString mStatusMessage; uint mProgress; QString mProgressMessage; bool mNeedsNetwork; bool mOnline; QSettings *mSettings; ChangeRecorder *mMonitor; org::freedesktop::Akonadi::Tracer *mTracer; AgentBase::Observer *mObserver; protected Q_SLOTS: - virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); - - virtual void itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ); - - virtual void itemRemoved( const Akonadi::Item &item ); - - virtual void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); - - virtual void collectionChanged( const Akonadi::Collection &collection ); - - virtual void collectionRemoved( const Akonadi::Collection &collection ); + void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + void itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ); + void itemMoved( const Akonadi::Item &, const Akonadi::Collection &source, const Akonadi::Collection &destination ); + void itemRemoved( const Akonadi::Item &item ); + + void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + void collectionChanged( const Akonadi::Collection &collection ); + void collectionRemoved( const Akonadi::Collection &collection ); }; } #endif diff --git a/akonadi/agentmanager.cpp b/akonadi/agentmanager.cpp index 6e59019e2..be6bb20f5 100644 --- a/akonadi/agentmanager.cpp +++ b/akonadi/agentmanager.cpp @@ -1,357 +1,359 @@ /* Copyright (c) 2006-2008 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "agentmanager.h" #include "agentmanager_p.h" #include "agenttype_p.h" #include "agentinstance_p.h" #include "collection.h" #include using namespace Akonadi; // @cond PRIVATE AgentInstance AgentManagerPrivate::createInstance( const AgentType &type ) { const QString &identifier = mManager->createAgentInstance( type.identifier() ); if ( identifier.isEmpty() ) return AgentInstance(); return fillAgentInstanceLight( identifier ); } void AgentManagerPrivate::agentTypeAdded( const QString &identifier ) { // Ignore agent types we already know about, for example because we called // readAgentTypes before. if ( mTypes.contains( identifier ) ) return; const AgentType type = fillAgentType( identifier ); if ( type.isValid() ) { mTypes.insert( identifier, type ); // The Akonadi ServerManager assumes that the server is up and running as soon // as it knows about at least one agent type. // If we emit the typeAdded() signal here, it therefore thinks the server is // running. However, the AgentManager does not know about all agent types yet, // as the server might still have pending agentTypeAdded() signals, even though // it internally knows all agent types already. // This can cause situations where the client gets told by the ServerManager that // the server is running, yet the client will encounter an error because the // AgentManager doesn't know all types yet. // // Therefore, we read all agent types from the server here so they are known. readAgentTypes(); emit mParent->typeAdded( type ); } } void AgentManagerPrivate::agentTypeRemoved( const QString &identifier ) { if ( !mTypes.contains( identifier ) ) return; const AgentType type = mTypes.take( identifier ); emit mParent->typeRemoved( type ); } void AgentManagerPrivate::agentInstanceAdded( const QString &identifier ) { const AgentInstance instance = fillAgentInstance( identifier ); if ( instance.isValid() ) { // It is possible that this function is called when the instance is already // in our list we filled initially in the constructor. // This happens when the constructor is called during Akonadi startup, when // the agent processes are not fully loaded and have no D-Bus interface yet. // The server-side agent manager then emits the instance added signal when // the D-Bus interface for the agent comes up. // In this case, we simply notify that the instance status has changed. bool newAgentInstance = !mInstances.contains( identifier ); if ( newAgentInstance ) { mInstances.insert( identifier, instance ); emit mParent->instanceAdded( instance ); } else { mInstances.remove( identifier ); mInstances.insert( identifier, instance ); emit mParent->instanceStatusChanged( instance ); } } } void AgentManagerPrivate::agentInstanceRemoved( const QString &identifier ) { if ( !mInstances.contains( identifier ) ) return; const AgentInstance instance = mInstances.take( identifier ); emit mParent->instanceRemoved( instance ); } void AgentManagerPrivate::agentInstanceStatusChanged( const QString &identifier, int status, const QString &msg ) { if ( !mInstances.contains( identifier ) ) return; AgentInstance &instance = mInstances[ identifier ]; instance.d->mStatus = status; instance.d->mStatusMessage = msg; emit mParent->instanceStatusChanged( instance ); } void AgentManagerPrivate::agentInstanceProgressChanged( const QString &identifier, uint progress, const QString &msg ) { if ( !mInstances.contains( identifier ) ) return; AgentInstance &instance = mInstances[ identifier ]; instance.d->mProgress = progress; instance.d->mStatusMessage = msg; emit mParent->instanceProgressChanged( instance ); } void AgentManagerPrivate::agentInstanceWarning( const QString &identifier, const QString &msg ) { if ( !mInstances.contains( identifier ) ) return; AgentInstance &instance = mInstances[ identifier ]; emit mParent->instanceWarning( instance, msg ); } void AgentManagerPrivate::agentInstanceError( const QString &identifier, const QString &msg ) { if ( !mInstances.contains( identifier ) ) return; AgentInstance &instance = mInstances[ identifier ]; emit mParent->instanceError( instance, msg ); } void AgentManagerPrivate::agentInstanceOnlineChanged( const QString &identifier, bool state ) { if ( !mInstances.contains( identifier ) ) return; AgentInstance &instance = mInstances[ identifier ]; instance.d->mIsOnline = state; emit mParent->instanceOnline( instance, state ); } void AgentManagerPrivate::agentInstanceNameChanged( const QString &identifier, const QString &name ) { if ( !mInstances.contains( identifier ) ) return; AgentInstance &instance = mInstances[ identifier ]; instance.d->mName = name; emit mParent->instanceNameChanged( instance ); } void AgentManagerPrivate::readAgentTypes() { QDBusReply types = mManager->agentTypes(); if ( types.isValid() ) { foreach ( const QString &type, types.value() ) { if ( !mTypes.contains( type ) ) agentTypeAdded( type ); } } } AgentType AgentManagerPrivate::fillAgentType( const QString &identifier ) const { AgentType type; type.d->mIdentifier = identifier; type.d->mName = mManager->agentName( identifier ); type.d->mDescription = mManager->agentComment( identifier ); type.d->mIconName = mManager->agentIcon( identifier ); type.d->mMimeTypes = mManager->agentMimeTypes( identifier ); type.d->mCapabilities = mManager->agentCapabilities( identifier ); return type; } void AgentManagerPrivate::setName( const AgentInstance &instance, const QString &name ) { mManager->setAgentInstanceName( instance.identifier(), name ); } void AgentManagerPrivate::setOnline( const AgentInstance &instance, bool state ) { mManager->setAgentInstanceOnline( instance.identifier(), state ); } void AgentManagerPrivate::configure( const AgentInstance &instance, QWidget *parent ) { qlonglong winId = 0; if ( parent ) winId = (qlonglong)( parent->window()->winId() ); mManager->agentInstanceConfigure( instance.identifier(), winId ); } void AgentManagerPrivate::synchronize( const AgentInstance &instance ) { mManager->agentInstanceSynchronize( instance.identifier() ); } void AgentManagerPrivate::synchronizeCollectionTree( const AgentInstance &instance ) { mManager->agentInstanceSynchronizeCollectionTree( instance.identifier() ); } AgentInstance AgentManagerPrivate::fillAgentInstance( const QString &identifier ) const { AgentInstance instance; const QString agentTypeIdentifier = mManager->agentInstanceType( identifier ); if ( !mTypes.contains( agentTypeIdentifier ) ) return instance; instance.d->mType = mTypes.value( agentTypeIdentifier ); instance.d->mIdentifier = identifier; instance.d->mName = mManager->agentInstanceName( identifier ); instance.d->mStatus = mManager->agentInstanceStatus( identifier ); instance.d->mStatusMessage = mManager->agentInstanceStatusMessage( identifier ); instance.d->mProgress = mManager->agentInstanceProgress( identifier ); instance.d->mIsOnline = mManager->agentInstanceOnline( identifier ); return instance; } AgentInstance AgentManagerPrivate::fillAgentInstanceLight( const QString &identifier ) const { AgentInstance instance; const QString agentTypeIdentifier = mManager->agentInstanceType( identifier ); Q_ASSERT_X( mTypes.contains( agentTypeIdentifier ), "fillAgentInstanceLight", "Requests non-existing agent type" ); instance.d->mType = mTypes.value( agentTypeIdentifier ); instance.d->mIdentifier = identifier; return instance; } AgentManager* AgentManagerPrivate::mSelf = 0; AgentManager::AgentManager() : QObject( 0 ), d( new AgentManagerPrivate( this ) ) { d->mManager = new org::freedesktop::Akonadi::AgentManager( QLatin1String( "org.freedesktop.Akonadi.Control" ), QLatin1String( "/AgentManager" ), QDBusConnection::sessionBus(), this ); connect( d->mManager, SIGNAL( agentTypeAdded( const QString& ) ), this, SLOT( agentTypeAdded( const QString& ) ) ); connect( d->mManager, SIGNAL( agentTypeRemoved( const QString& ) ), this, SLOT( agentTypeRemoved( const QString& ) ) ); connect( d->mManager, SIGNAL( agentInstanceAdded( const QString& ) ), this, SLOT( agentInstanceAdded( const QString& ) ) ); connect( d->mManager, SIGNAL( agentInstanceRemoved( const QString& ) ), this, SLOT( agentInstanceRemoved( const QString& ) ) ); connect( d->mManager, SIGNAL( agentInstanceStatusChanged( const QString&, int, const QString& ) ), this, SLOT( agentInstanceStatusChanged( const QString&, int, const QString& ) ) ); connect( d->mManager, SIGNAL( agentInstanceProgressChanged( const QString&, uint, const QString& ) ), this, SLOT( agentInstanceProgressChanged( const QString&, uint, const QString& ) ) ); connect( d->mManager, SIGNAL( agentInstanceNameChanged( const QString&, const QString& ) ), this, SLOT( agentInstanceNameChanged( const QString&, const QString& ) ) ); connect( d->mManager, SIGNAL( agentInstanceWarning( const QString&, const QString& ) ), this, SLOT( agentInstanceWarning( const QString&, const QString& ) ) ); connect( d->mManager, SIGNAL( agentInstanceError( const QString&, const QString& ) ), this, SLOT( agentInstanceError( const QString&, const QString& ) ) ); connect( d->mManager, SIGNAL(agentInstanceOnlineChanged(QString,bool)), SLOT(agentInstanceOnlineChanged(QString,bool)) ); - QDBusReply result = d->mManager->agentTypes(); - if ( result.isValid() ) { - foreach( const QString &type, result.value() ) { - const AgentType agentType = d->fillAgentType( type ); - d->mTypes.insert( type, agentType ); + if ( d->mManager->isValid() ) { + QDBusReply result = d->mManager->agentTypes(); + if ( result.isValid() ) { + foreach( const QString &type, result.value() ) { + const AgentType agentType = d->fillAgentType( type ); + d->mTypes.insert( type, agentType ); + } } - } - result = d->mManager->agentInstances(); - if ( result.isValid() ) { - foreach( const QString &instance, result.value() ) { - const AgentInstance agentInstance = d->fillAgentInstance( instance ); - d->mInstances.insert( instance, agentInstance ); + result = d->mManager->agentInstances(); + if ( result.isValid() ) { + foreach( const QString &instance, result.value() ) { + const AgentInstance agentInstance = d->fillAgentInstance( instance ); + d->mInstances.insert( instance, agentInstance ); + } } } } // @endcond AgentManager::~AgentManager() { delete d; } AgentManager* AgentManager::self() { if ( !AgentManagerPrivate::mSelf ) AgentManagerPrivate::mSelf = new AgentManager(); return AgentManagerPrivate::mSelf; } AgentType::List AgentManager::types() const { return d->mTypes.values(); } AgentType AgentManager::type( const QString &identifier ) const { return d->mTypes.value( identifier ); } AgentInstance::List AgentManager::instances() const { return d->mInstances.values(); } AgentInstance AgentManager::instance( const QString &identifier ) const { return d->mInstances.value( identifier ); } void AgentManager::removeInstance( const AgentInstance &instance ) { d->mManager->removeAgentInstance( instance.identifier() ); } void AgentManager::synchronizeCollection(const Collection & collection) { const QString resId = collection.resource(); Q_ASSERT( !resId.isEmpty() ); d->mManager->agentInstanceSynchronizeCollection( resId, collection.id() ); } #include "agentmanager.moc" diff --git a/akonadi/collection.cpp b/akonadi/collection.cpp index 2804bf6ab..9ffef9665 100644 --- a/akonadi/collection.cpp +++ b/akonadi/collection.cpp @@ -1,214 +1,221 @@ /* Copyright (c) 2006 - 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "collection.h" #include "collection_p.h" #include "attributefactory.h" #include "cachepolicy.h" #include "collectionrightsattribute_p.h" #include "collectionstatistics.h" #include "entity_p.h" #include #include #include #include #include #include using namespace Akonadi; class CollectionRoot : public Collection { public: - CollectionRoot() : Collection( 0 ) { + CollectionRoot() + : Collection( 0 ) + { QStringList types; types << Collection::mimeType(); setContentMimeTypes( types ); + + // The root collection is read-only for the users + Collection::Rights rights; + rights |= Collection::ReadOnly; + setRights( rights ); } }; K_GLOBAL_STATIC( CollectionRoot, s_root ) Collection::Collection() : Entity( new CollectionPrivate ) { Q_D( Collection ); static int lastId = -1; d->mId = lastId--; } Collection::Collection( Id id ) : Entity( new CollectionPrivate( id ) ) { } Collection::Collection(const Collection & other) : Entity( other ) { } Collection::~Collection() { } QString Collection::name( ) const { return d_func()->name; } void Collection::setName( const QString & name ) { Q_D( Collection ); d->name = name; } Collection::Rights Collection::rights() const { CollectionRightsAttribute *attr = attribute(); if ( attr ) return attr->rights(); else return AllRights; } void Collection::setRights( Rights rights ) { CollectionRightsAttribute *attr = attribute( AddIfMissing ); attr->setRights( rights ); } QStringList Collection::contentMimeTypes() const { return d_func()->contentTypes; } void Collection::setContentMimeTypes( const QStringList & types ) { Q_D( Collection ); d->contentTypes = types; d->contentTypesChanged = true; } Collection::Id Collection::parent() const { return d_func()->parentId; } void Collection::setParent( Id parent ) { Q_D( Collection ); d->parentId = parent; } void Collection::setParent(const Collection & collection) { Q_D( Collection ); d->parentId = collection.id(); d->parentRemoteId = collection.remoteId(); } QString Collection::parentRemoteId() const { return d_func()->parentRemoteId; } void Collection::setParentRemoteId(const QString & remoteParent) { Q_D( Collection ); d->parentRemoteId = remoteParent; } KUrl Collection::url() const { KUrl url; url.setProtocol( QString::fromLatin1("akonadi") ); url.addQueryItem( QLatin1String("collection"), QString::number( id() ) ); return url; } Collection Collection::fromUrl( const KUrl &url ) { if ( url.protocol() != QLatin1String( "akonadi" ) ) return Collection(); const QString colStr = url.queryItem( QLatin1String( "collection" ) ); bool ok = false; Collection::Id colId = colStr.toLongLong( &ok ); if ( !ok ) return Collection(); if ( colId == 0 ) return Collection::root(); return Collection( colId ); } Collection Collection::root() { return *s_root; } QString Collection::mimeType( ) { return QString::fromLatin1("inode/directory"); } QString Collection::resource() const { return d_func()->resource; } void Collection::setResource(const QString & resource) { Q_D( Collection ); d->resource = resource; } uint qHash( const Akonadi::Collection &collection ) { return qHash( collection.id() ); } CollectionStatistics Collection::statistics() const { return d_func()->statistics; } void Collection::setStatistics(const CollectionStatistics & statistics) { Q_D( Collection ); d->statistics = statistics; } CachePolicy Collection::cachePolicy() const { return d_func()->cachePolicy; } void Collection::setCachePolicy(const CachePolicy & cachePolicy) { Q_D( Collection ); d->cachePolicy = cachePolicy; d->cachePolicyChanged = true; } AKONADI_DEFINE_PRIVATE( Akonadi::Collection ) diff --git a/akonadi/item.cpp b/akonadi/item.cpp index 8650e5ad3..0cdb49114 100644 --- a/akonadi/item.cpp +++ b/akonadi/item.cpp @@ -1,210 +1,212 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "item.h" #include "item_p.h" #include "itemserializer_p.h" #include "protocol_p.h" #include #include using namespace Akonadi; // Change to something != RFC822 as soon as the server supports it const char* Item::FullPayload = "RFC822"; Item::Item() : Entity( new ItemPrivate ) { } Item::Item( Id id ) : Entity( new ItemPrivate( id ) ) { } Item::Item( const QString & mimeType ) : Entity( new ItemPrivate ) { d_func()->mMimeType = mimeType; } Item::Item( const Item &other ) : Entity( other ) { } Item::~Item() { } Item::Flags Item::flags() const { return d_func()->mFlags; } void Item::setFlag( const QByteArray & name ) { Q_D( Item ); d->mFlags.insert( name ); if ( !d->mFlagsOverwritten ) d->mAddedFlags.insert( name ); } void Item::clearFlag( const QByteArray & name ) { Q_D( Item ); d->mFlags.remove( name ); if ( !d->mFlagsOverwritten ) d->mDeletedFlags.insert( name ); } void Item::setFlags( const Flags &flags ) { Q_D( Item ); d->mFlags = flags; d->mFlagsOverwritten = true; } void Item::clearFlags() { Q_D( Item ); d->mFlags.clear(); d->mFlagsOverwritten = true; } QDateTime Item::modificationTime() const { return d_func()->mModificationTime; } void Item::setModificationTime( const QDateTime &datetime ) { d_func()->mModificationTime = datetime; } bool Item::hasFlag( const QByteArray & name ) const { return d_func()->mFlags.contains( name ); } QSet Item::loadedPayloadParts() const { return ItemSerializer::parts( *this ); } QByteArray Item::payloadData() const { int version = 0; QByteArray data; ItemSerializer::serialize( *this, FullPayload, data, version ); return data; } void Item::setPayloadFromData( const QByteArray &data ) { ItemSerializer::deserialize( *this, FullPayload, data, 0, false ); } int Item::revision() const { return d_func()->mRevision; } void Item::setRevision( int rev ) { d_func()->mRevision = rev; } Entity::Id Item::collectionId() const { return d_func()->mCollectionId; } void Item::setCollectionId( Entity::Id collectionId ) { d_func()->mCollectionId = collectionId; } QString Item::mimeType() const { return d_func()->mMimeType; } void Item::setSize( qint64 size ) { - d_func()->mSize = size; + Q_D( Item ); + d->mSize = size; + d->mSizeChanged = true; } qint64 Item::size() const { return d_func()->mSize; } void Item::setMimeType( const QString & mimeType ) { d_func()->mMimeType = mimeType; } bool Item::hasPayload() const { return d_func()->mPayload != 0; } KUrl Item::url( UrlType type ) const { KUrl url; url.setProtocol( QString::fromLatin1("akonadi") ); url.addQueryItem( QLatin1String( "item" ), QString::number( id() ) ); if ( type == UrlWithMimeType ) url.addQueryItem( QLatin1String( "type" ), mimeType() ); return url; } Item Item::fromUrl( const KUrl &url ) { if ( url.protocol() != QLatin1String( "akonadi" ) ) return Item(); const QString itemStr = url.queryItem( QLatin1String( "item" ) ); bool ok = false; Item::Id itemId = itemStr.toLongLong( &ok ); if ( !ok ) return Item(); return Item( itemId ); } PayloadBase* Item::payloadBase() const { return d_func()->mPayload; } void Item::setPayloadBase( PayloadBase* p ) { Q_D( Item ); delete d->mPayload; d->mPayload = p; } AKONADI_DEFINE_PRIVATE( Item ) diff --git a/akonadi/item_p.h b/akonadi/item_p.h index 768ead1f5..75bdf8226 100644 --- a/akonadi/item_p.h +++ b/akonadi/item_p.h @@ -1,97 +1,101 @@ /* Copyright (c) 2008 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_ITEM_P_H #define AKONADI_ITEM_P_H #include #include #include "entity_p.h" #include "itempayloadinternals_p.h" namespace Akonadi { /** * @internal */ class ItemPrivate : public EntityPrivate { public: ItemPrivate( Item::Id id = -1 ) : EntityPrivate( id ), mPayload( 0 ), mRevision( -1 ), mCollectionId( -1 ), mSize( 0 ), mModificationTime(), - mFlagsOverwritten( false ) + mFlagsOverwritten( false ), + mSizeChanged( false ) { } ItemPrivate( const ItemPrivate &other ) : EntityPrivate( other ) { mFlags = other.mFlags; mRevision = other.mRevision; mSize = other.mSize; mModificationTime = other.mModificationTime; mMimeType = other.mMimeType; if ( other.mPayload ) mPayload = other.mPayload->clone(); else mPayload = 0; mAddedFlags = other.mAddedFlags; mDeletedFlags = other.mDeletedFlags; mFlagsOverwritten = other.mFlagsOverwritten; + mSizeChanged = other.mSizeChanged; } ~ItemPrivate() { delete mPayload; } void resetChangeLog() { mFlagsOverwritten = false; mAddedFlags.clear(); mDeletedFlags.clear(); + mSizeChanged = false; } EntityPrivate *clone() const { return new ItemPrivate( *this ); } PayloadBase* mPayload; Item::Flags mFlags; int mRevision; Entity::Id mCollectionId; qint64 mSize; QDateTime mModificationTime; QString mMimeType; Item::Flags mAddedFlags; Item::Flags mDeletedFlags; bool mFlagsOverwritten; + bool mSizeChanged; }; } #endif diff --git a/akonadi/itemmodifyjob.cpp b/akonadi/itemmodifyjob.cpp index 157838f8f..c274684bc 100644 --- a/akonadi/itemmodifyjob.cpp +++ b/akonadi/itemmodifyjob.cpp @@ -1,230 +1,231 @@ /* Copyright (c) 2006 - 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "collection.h" #include "entity_p.h" #include "imapparser_p.h" #include "itemserializer_p.h" #include "job_p.h" #include "item_p.h" #include "protocolhelper_p.h" #include using namespace Akonadi; ItemModifyJobPrivate::ItemModifyJobPrivate( ItemModifyJob *parent, const Item &item ) : JobPrivate( parent ), mItem( item ), mRevCheck( true ), mIgnorePayload( false ) { mParts = mItem.loadedPayloadParts(); } void ItemModifyJobPrivate::setClean() { mOperations.insert( Dirty ); } QByteArray ItemModifyJobPrivate::nextPartHeader() { QByteArray command; if ( !mParts.isEmpty() ) { QSetIterator it( mParts ); const QByteArray label = it.next(); mParts.remove( label ); mPendingData.clear(); int version = 0; ItemSerializer::serialize( mItem, label, mPendingData, version ); command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, label, version ); command += ".SILENT"; if ( mPendingData.size() > 0 ) { command += " {" + QByteArray::number( mPendingData.size() ) + "}\n"; } else { if ( mPendingData.isNull() ) command += " NIL"; else command += " \"\""; command += nextPartHeader(); } } else { command += ")\n"; } return command; } ItemModifyJob::ItemModifyJob( const Item &item, QObject * parent ) : Job( new ItemModifyJobPrivate( this, item ), parent ) { Q_D( ItemModifyJob ); d->mOperations.insert( ItemModifyJobPrivate::RemoteId ); } ItemModifyJob::~ItemModifyJob() { } void ItemModifyJob::doStart() { Q_D( ItemModifyJob ); QList changes; foreach ( int op, d->mOperations ) { switch ( op ) { case ItemModifyJobPrivate::RemoteId: if ( !d->mItem.remoteId().isNull() ) { changes << "REMOTEID.SILENT"; changes << ImapParser::quote( d->mItem.remoteId().toUtf8() ); } break; case ItemModifyJobPrivate::Dirty: changes << "DIRTY.SILENT"; changes << "false"; break; } } if ( d->mItem.d_func()->mFlagsOverwritten ) { changes << "FLAGS.SILENT"; changes << '(' + ImapParser::join( d->mItem.flags(), " " ) + ')'; } else { if ( !d->mItem.d_func()->mAddedFlags.isEmpty() ) { changes << "+FLAGS.SILENT"; changes << '(' + ImapParser::join( d->mItem.d_func()->mAddedFlags, " " ) + ')'; } if ( !d->mItem.d_func()->mDeletedFlags.isEmpty() ) { changes << "-FLAGS.SILENT"; changes << '(' + ImapParser::join( d->mItem.d_func()->mDeletedFlags, " " ) + ')'; } } if ( !d->mItem.d_func()->mDeletedAttributes.isEmpty() ) { changes << "-PARTS.SILENT"; QList attrs; foreach ( const QByteArray &attr, d->mItem.d_func()->mDeletedAttributes ) attrs << ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, attr ); changes << '(' + ImapParser::join( attrs, " " ) + ')'; } // nothing to do if ( changes.isEmpty() && d->mParts.isEmpty() && d->mItem.attributes().isEmpty() ) { emitResult(); return; } d->mTag = d->newTag(); QByteArray command = d->mTag; command += " UID STORE " + QByteArray::number( d->mItem.id() ) + ' '; if ( !d->mRevCheck ) { command += "NOREV "; } else { command += "REV " + QByteArray::number( d->mItem.revision() ) + ' '; } - command += "SIZE " + QByteArray::number( d->mItem.size() ); + if ( d->mItem.d_func()->mSizeChanged ) + command += "SIZE " + QByteArray::number( d->mItem.size() ); command += " (" + ImapParser::join( changes, " " ); const QByteArray attrs = ProtocolHelper::attributesToByteArray( d->mItem, true ); if ( !attrs.isEmpty() ) command += ' ' + attrs; command += d->nextPartHeader(); d->writeData( command ); d->newTag(); // hack to circumvent automatic response handling } void ItemModifyJob::doHandleResponse(const QByteArray &_tag, const QByteArray & data) { Q_D( ItemModifyJob ); if ( _tag == "+" ) { // ready for literal data d->writeData( d->mPendingData ); d->writeData( d->nextPartHeader() ); return; } if ( _tag == d->mTag ) { if ( data.startsWith( "OK" ) ) { //krazy:exclude=strings QDateTime modificationDateTime; int dateTimePos = data.indexOf( "DATETIME" ); if ( dateTimePos != -1 ) { int resultPos = ImapParser::parseDateTime( data, modificationDateTime, dateTimePos + 8 ); if ( resultPos == (dateTimePos + 8) ) { kDebug( 5250 ) << "Invalid DATETIME response to STORE command: " << _tag << data; } } // increase item revision of own copy of item d->mItem.setRevision( d->mItem.revision() + 1 ); d->mItem.setModificationTime( modificationDateTime ); d->mItem.d_ptr->resetChangeLog(); } else { setError( Unknown ); setErrorText( QString::fromUtf8( data ) ); } emitResult(); return; } kDebug( 5250 ) << "Unhandled response: " << _tag << data; } void ItemModifyJob::setIgnorePayload( bool ignore ) { Q_D( ItemModifyJob ); if ( d->mIgnorePayload == ignore ) return; d->mIgnorePayload = ignore; if ( d->mIgnorePayload ) d->mParts = QSet(); else { Q_ASSERT( !d->mItem.mimeType().isEmpty() ); d->mParts = d->mItem.loadedPayloadParts(); } } bool ItemModifyJob::ignorePayload() const { Q_D( const ItemModifyJob ); return d->mIgnorePayload; } void ItemModifyJob::disableRevisionCheck() { Q_D( ItemModifyJob ); d->mRevCheck = false; } Item ItemModifyJob::item() const { Q_D( const ItemModifyJob ); return d->mItem; } #include "itemmodifyjob.moc" diff --git a/akonadi/resourcebase.cpp b/akonadi/resourcebase.cpp index 486b58477..eaa2384d9 100644 --- a/akonadi/resourcebase.cpp +++ b/akonadi/resourcebase.cpp @@ -1,575 +1,624 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "resourcebase.h" #include "agentbase_p.h" #include "resourceadaptor.h" #include "collectiondeletejob.h" #include "collectionsync_p.h" #include "itemsync.h" #include "resourcescheduler_p.h" #include "tracerinterface.h" #include "xdgbasedirs_p.h" #include "changerecorder.h" #include "collectionfetchjob.h" #include "collectionmodifyjob.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "session.h" #include "resourceselectjob_p.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; class Akonadi::ResourceBasePrivate : public AgentBasePrivate { public: ResourceBasePrivate( ResourceBase *parent ) : AgentBasePrivate( parent ), scheduler( 0 ), - mItemSyncer( 0 ) + mItemSyncer( 0 ), + mCollectionSyncer( 0 ) { mStatusMessage = defaultReadyMessage(); } Q_DECLARE_PUBLIC( ResourceBase ) void delayedInit() { if ( !QDBusConnection::sessionBus().registerService( QLatin1String( "org.freedesktop.Akonadi.Resource." ) + mId ) ) kFatal() << "Unable to register service at D-Bus: " << QDBusConnection::sessionBus().lastError().message(); AgentBasePrivate::delayedInit(); } virtual void changeProcessed() { mMonitor->changeProcessed(); if ( !mMonitor->isEmpty() ) scheduler->scheduleChangeReplay(); scheduler->taskDone(); } void slotDeliveryDone( KJob* job ); void slotCollectionSyncDone( KJob *job ); void slotLocalListDone( KJob *job ); void slotSynchronizeCollection( const Collection &col ); void slotCollectionListDone( KJob *job ); void slotItemSyncDone( KJob *job ); void slotPercent( KJob* job, unsigned long percent ); void slotDeleteResourceCollection(); void slotDeleteResourceCollectionDone( KJob *job ); void slotCollectionDeletionDone( KJob *job ); QString mName; // synchronize states Collection currentCollection; ResourceScheduler *scheduler; ItemSync *mItemSyncer; + CollectionSync *mCollectionSyncer; }; ResourceBase::ResourceBase( const QString & id ) : AgentBase( new ResourceBasePrivate( this ), id ) { Q_D( ResourceBase ); new ResourceAdaptor( this ); const QString name = d->mSettings->value( QLatin1String( "Resource/Name" ) ).toString(); if ( !name.isEmpty() ) d->mName = name; d->scheduler = new ResourceScheduler( this ); d->mMonitor->setChangeRecordingEnabled( true ); connect( d->mMonitor, SIGNAL(changesAdded()), d->scheduler, SLOT(scheduleChangeReplay()) ); d->mMonitor->setResourceMonitored( d->mId.toLatin1() ); connect( d->scheduler, SIGNAL(executeFullSync()), SLOT(retrieveCollections()) ); connect( d->scheduler, SIGNAL(executeCollectionTreeSync()), SLOT(retrieveCollections()) ); connect( d->scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection)), SLOT(slotSynchronizeCollection(Akonadi::Collection)) ); connect( d->scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QSet)), SLOT(retrieveItem(Akonadi::Item,QSet)) ); connect( d->scheduler, SIGNAL(executeResourceCollectionDeletion()), SLOT(slotDeleteResourceCollection()) ); connect( d->scheduler, SIGNAL( status( int, QString ) ), SIGNAL( status( int, QString ) ) ); connect( d->scheduler, SIGNAL(executeChangeReplay()), d->mMonitor, SLOT(replayNext()) ); connect( d->scheduler, SIGNAL(fullSyncComplete()), SIGNAL(synchronized()) ); connect( d->mMonitor, SIGNAL(nothingToReplay()), d->scheduler, SLOT(taskDone()) ); connect( this, SIGNAL(synchronized()), d->scheduler, SLOT(taskDone()) ); d->scheduler->setOnline( d->mOnline ); if ( !d->mMonitor->isEmpty() ) d->scheduler->scheduleChangeReplay(); new ResourceSelectJob( identifier() ); } ResourceBase::~ResourceBase() { } void ResourceBase::synchronize() { d_func()->scheduler->scheduleFullSync(); } void ResourceBase::setName( const QString &name ) { Q_D( ResourceBase ); if ( name == d->mName ) return; // TODO: rename collection d->mName = name; if ( d->mName.isEmpty() || d->mName == d->mId ) d->mSettings->remove( QLatin1String( "Resource/Name" ) ); else d->mSettings->setValue( QLatin1String( "Resource/Name" ), d->mName ); d->mSettings->sync(); emit nameChanged( d->mName ); } QString ResourceBase::name() const { Q_D( const ResourceBase ); if ( d->mName.isEmpty() ) return d->mId; else return d->mName; } static char* sAppName = 0; QString ResourceBase::parseArguments( int argc, char **argv ) { QString identifier; if ( argc < 3 ) { kDebug( 5250 ) << "Not enough arguments passed..."; exit( 1 ); } for ( int i = 1; i < argc - 1; ++i ) { if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) identifier = QLatin1String( argv[ i + 1 ] ); } if ( identifier.isEmpty() ) { kDebug( 5250 ) << "Identifier argument missing"; exit( 1 ); } sAppName = qstrdup( identifier.toLatin1().constData() ); - KCmdLineArgs::init( argc, argv, sAppName, 0, + KCmdLineArgs::init( argc, argv, sAppName, QByteArray( argv[ 0 ] ), ki18nc("@title, application name", "Akonadi Resource"), "0.1", ki18nc("@title, application description", "Akonadi Resource") ); KCmdLineOptions options; options.add("identifier ", ki18nc("@label, commandline option", "Resource identifier")); KCmdLineArgs::addCmdLineOptions( options ); return identifier; } int ResourceBase::init( ResourceBase *r ) { QApplication::setQuitOnLastWindowClosed( false ); int rv = kapp->exec(); delete r; delete[] sAppName; return rv; } void ResourceBase::itemRetrieved( const Item &item ) { Q_D( ResourceBase ); Q_ASSERT( d->scheduler->currentTask().type == ResourceScheduler::FetchItem ); if ( !item.isValid() ) { QDBusMessage reply( d->scheduler->currentTask().dbusMsg ); reply << false; QDBusConnection::sessionBus().send( reply ); d->scheduler->taskDone(); return; } Item i( item ); QSet requestedParts = d->scheduler->currentTask().itemParts; foreach ( const QByteArray &part, requestedParts ) { if ( !item.loadedPayloadParts().contains( part ) ) { kWarning( 5250 ) << "Item does not provide part" << part; } } ItemModifyJob *job = new ItemModifyJob( i ); // FIXME: remove once the item with which we call retrieveItem() has a revision number job->disableRevisionCheck(); connect( job, SIGNAL(result(KJob*)), SLOT(slotDeliveryDone(KJob*)) ); } void ResourceBasePrivate::slotDeliveryDone(KJob * job) { Q_Q( ResourceBase ); Q_ASSERT( scheduler->currentTask().type == ResourceScheduler::FetchItem ); QDBusMessage reply( scheduler->currentTask().dbusMsg ); if ( job->error() ) { emit q->error( QLatin1String( "Error while creating item: " ) + job->errorString() ); reply << false; } else { reply << true; } QDBusConnection::sessionBus().send( reply ); scheduler->taskDone(); } void ResourceBasePrivate::slotDeleteResourceCollection() { Q_Q( ResourceBase ); CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); job->setResource( q->identifier() ); connect( job, SIGNAL(result(KJob*)), q, SLOT(slotDeleteResourceCollectionDone(KJob*)) ); } void ResourceBasePrivate::slotDeleteResourceCollectionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); scheduler->taskDone(); } else { const CollectionFetchJob *fetchJob = static_cast( job ); if ( !fetchJob->collections().isEmpty() ) { CollectionDeleteJob *job = new CollectionDeleteJob( fetchJob->collections().first() ); connect( job, SIGNAL( result( KJob* ) ), q, SLOT( slotCollectionDeletionDone( KJob* ) ) ); } else { // there is no resource collection, so just ignore the request scheduler->taskDone(); } } } void ResourceBasePrivate::slotCollectionDeletionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBase::changeCommitted(const Item& item) { Q_D( ResourceBase ); ItemModifyJob *job = new ItemModifyJob( item ); job->d_func()->setClean(); job->disableRevisionCheck(); // TODO: remove, but where/how do we handle the error? job->ignorePayload(); // we only want to reset the dirty flag and update the remote id d->changeProcessed(); } void ResourceBase::changeCommitted( const Collection &collection ) { Q_D( ResourceBase ); CollectionModifyJob *job = new CollectionModifyJob( collection ); Q_UNUSED( job ); //TODO: error checking d->changeProcessed(); } bool ResourceBase::requestItemDelivery( qint64 uid, const QString & remoteId, const QString &mimeType, const QStringList &_parts ) { Q_D( ResourceBase ); if ( !isOnline() ) { emit error( i18nc( "@info", "Cannot fetch item in offline mode." ) ); return false; } setDelayedReply( true ); // FIXME: we need at least the revision number too Item item( uid ); item.setMimeType( mimeType ); item.setRemoteId( remoteId ); QSet parts; Q_FOREACH( const QString &str, _parts ) parts.insert( str.toLatin1() ); d->scheduler->scheduleItemFetch( item, parts, message().createReply() ); return true; } void ResourceBase::collectionsRetrieved(const Collection::List & collections) { Q_D( ResourceBase ); - CollectionSync *syncer = new CollectionSync( d->mId ); - syncer->setRemoteCollections( collections ); - connect( syncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); + Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || + d->scheduler->currentTask().type == ResourceScheduler::SyncAll, + "ResourceBase::collectionsRetrieved()", + "Calling collectionsRetrieved() although no collection retrieval is in progress" ); + if ( !d->mCollectionSyncer ) { + d->mCollectionSyncer = new CollectionSync( identifier() ); + connect( d->mCollectionSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); + connect( d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); + } + d->mCollectionSyncer->setRemoteCollections( collections ); } void ResourceBase::collectionsRetrievedIncremental(const Collection::List & changedCollections, const Collection::List & removedCollections) { Q_D( ResourceBase ); - CollectionSync *syncer = new CollectionSync( d->mId ); - syncer->setRemoteCollections( changedCollections, removedCollections ); - connect( syncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); + Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || + d->scheduler->currentTask().type == ResourceScheduler::SyncAll, + "ResourceBase::collectionsRetrievedIncremental()", + "Calling collectionsRetrievedIncremental() although no collection retrieval is in progress" ); + if ( !d->mCollectionSyncer ) { + d->mCollectionSyncer = new CollectionSync( identifier() ); + connect( d->mCollectionSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); + connect( d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); + } + d->mCollectionSyncer->setRemoteCollections( changedCollections, removedCollections ); +} + +void ResourceBase::setCollectionStreamingEnabled(bool enable) +{ + Q_D( ResourceBase ); + Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || + d->scheduler->currentTask().type == ResourceScheduler::SyncAll, + "ResourceBase::setCollectionStreamingEnabled()", + "Calling setCollectionStreamingEnabled() although no collection retrieval is in progress" ); + if ( !d->mCollectionSyncer ) { + d->mCollectionSyncer = new CollectionSync( identifier() ); + connect( d->mCollectionSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); + connect( d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); + } + d->mCollectionSyncer->setStreamingEnabled( enable ); +} + +void ResourceBase::collectionsRetrievalDone() +{ + Q_D( ResourceBase ); + Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || + d->scheduler->currentTask().type == ResourceScheduler::SyncAll, + "ResourceBase::collectionsRetrievalDone()", + "Calling collectionsRetrievalDone() although no collection retrieval is in progress" ); + // streaming enabled, so finalize the sync + if ( d->mCollectionSyncer ) { + d->mCollectionSyncer->retrievalDone(); + } + // user did the sync himself, we are done now + else { + d->scheduler->taskDone(); + } } void ResourceBasePrivate::slotCollectionSyncDone(KJob * job) { Q_Q( ResourceBase ); + mCollectionSyncer = 0; if ( job->error() ) { emit q->error( job->errorString() ); } else { if ( scheduler->currentTask().type == ResourceScheduler::SyncAll ) { CollectionFetchJob *list = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); list->setResource( mId ); q->connect( list, SIGNAL(result(KJob*)), q, SLOT(slotLocalListDone(KJob*)) ); return; } } scheduler->taskDone(); } void ResourceBasePrivate::slotLocalListDone(KJob * job) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } else { Collection::List cols = static_cast( job )->collections(); foreach ( const Collection &col, cols ) { scheduler->scheduleSync( col ); } scheduler->scheduleFullSyncCompletion(); } scheduler->taskDone(); } void ResourceBasePrivate::slotSynchronizeCollection( const Collection &col ) { Q_Q( ResourceBase ); currentCollection = col; // check if this collection actually can contain anything QStringList contentTypes = currentCollection.contentMimeTypes(); contentTypes.removeAll( Collection::mimeType() ); if ( !contentTypes.isEmpty() ) { emit q->status( AgentBase::Running, i18nc( "@info:status", "Syncing collection '%1'", currentCollection.name() ) ); q->retrieveItems( currentCollection ); return; } scheduler->taskDone(); } void ResourceBase::itemsRetrievalDone() { Q_D( ResourceBase ); // streaming enabled, so finalize the sync if ( d->mItemSyncer ) { d->mItemSyncer->deliveryDone(); } // user did the sync himself, we are done now else { d->scheduler->taskDone(); } } void ResourceBase::clearCache() { Q_D( ResourceBase ); d->scheduler->scheduleResourceCollectionDeletion(); } Collection ResourceBase::currentCollection() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection , "ResourceBase::currentCollection()", "Trying to access current collection although no item retrieval is in progress" ); return d->currentCollection; } Item ResourceBase::currentItem() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::FetchItem , "ResourceBase::currentItem()", "Trying to access current item although no item retrieval is in progress" ); return d->scheduler->currentTask().item; } void ResourceBase::synchronizeCollectionTree() { d_func()->scheduler->scheduleCollectionTreeSync(); } void ResourceBase::cancelTask() { Q_D( ResourceBase ); switch ( d->scheduler->currentTask().type ) { case ResourceScheduler::FetchItem: itemRetrieved( Item() ); // sends the error reply and break; case ResourceScheduler::ChangeReplay: d->changeProcessed(); break; default: d->scheduler->taskDone(); } } void ResourceBase::cancelTask( const QString &msg ) { cancelTask(); emit error( msg ); } void ResourceBase::deferTask() { Q_D( ResourceBase ); d->scheduler->deferTask(); } void ResourceBase::doSetOnline( bool state ) { d_func()->scheduler->setOnline( state ); } void ResourceBase::synchronizeCollection(qint64 collectionId ) { CollectionFetchJob* job = new CollectionFetchJob( Collection(collectionId), CollectionFetchJob::Base ); job->setResource( identifier() ); connect( job, SIGNAL(result(KJob*)), SLOT(slotCollectionListDone(KJob*)) ); } void ResourceBasePrivate::slotCollectionListDone( KJob *job ) { if ( !job->error() ) { Collection::List list = static_cast( job )->collections(); if ( !list.isEmpty() ) { Collection col = list.first(); scheduler->scheduleSync( col ); } } // TODO: error handling } void ResourceBase::setTotalItems( int amount ) { kDebug() << amount; Q_D( ResourceBase ); setItemStreamingEnabled( true ); d->mItemSyncer->setTotalItems( amount ); } void ResourceBase::setItemStreamingEnabled( bool enable ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::setItemStreamingEnabled()", "Calling setItemStreamingEnabled() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mItemSyncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); } d->mItemSyncer->setStreamingEnabled( enable ); } void ResourceBase::itemsRetrieved( const Item::List &items ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrieved()", "Calling itemsRetrieved() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mItemSyncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); } d->mItemSyncer->setFullSyncItems( items ); } void ResourceBase::itemsRetrievedIncremental(const Item::List &changedItems, const Item::List &removedItems) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrievedIncremental()", "Calling itemsRetrievedIncremental() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( d->mItemSyncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); } d->mItemSyncer->setIncrementalSyncItems( changedItems, removedItems ); } void ResourceBasePrivate::slotItemSyncDone( KJob *job ) { mItemSyncer = 0; Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBasePrivate::slotPercent( KJob *job, unsigned long percent ) { Q_Q( ResourceBase ); Q_UNUSED( job ); emit q->percent( percent ); } #include "resourcebase.moc" diff --git a/akonadi/resourcebase.h b/akonadi/resourcebase.h index 06e0a3f34..b92b44895 100644 --- a/akonadi/resourcebase.h +++ b/akonadi/resourcebase.h @@ -1,448 +1,467 @@ /* This file is part of akonadiresources. Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_RESOURCEBASE_H #define AKONADI_RESOURCEBASE_H #include "akonadi_export.h" #include #include #include class KJob; class ResourceAdaptor; namespace Akonadi { class ResourceBasePrivate; /** * @short The base class for all Akonadi resources. * * This class should be used as a base class by all resource agents, * because it encapsulates large parts of the protocol between * resource agent, agent manager and the Akonadi storage. * * It provides many convenience methods to make implementing a * new Akonadi resource agent as simple as possible. * *

How to write a resource

* * The following provides an overview of what you need to do to implement * your own Akonadi resource. In the following, the term 'backend' refers * to the entity the resource connects with Akonadi, be it a single file * or a remote server. * * @todo Complete this (online/offline state management) * *
Basic %Resource Framework
* * The following is needed to create a new resource: * - A new class deriving from Akonadi::ResourceBase, implementing at least all * pure-virtual methods, see below for further details. * - call init() in your main() function. * - a .desktop file similar to the following example * \code * [Desktop Entry] * Encoding=UTF-8 * Name=My Akonadi Resource * Type=AkonadiResource * Exec=akonadi_my_resource * Icon=my-icon * * X-Akonadi-MimeTypes= * X-Akonadi-Capabilities=Resource * X-Akonadi-Identifier=akonadi_my_resource * \endcode * *
Handling PIM Items
* * To follow item changes in the backend, the following steps are necessary: * - Implement retrieveItems() to synchronize all items in the given * collection. If the backend supports incremental retrieval, * implementing support for that is recommended to improve performance. * - Convert the items provided by the backend to Akonadi items. * This typically happens either in retrieveItems() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Item objects for every retrieved * item. It's very important that every object has its remote identifier set. * - Call itemsRetrieved() or itemsRetrievedIncremental() respectively * with the item objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any item in the Akonadi storage manually. * * To fetch item data on demand, the method retrieveItem() needs to be * reimplemented. Fetch the requested data there and call itemRetrieved() * with the result item. * * To write local changes back to the backend, you need to re-implement * the following three methods: * - itemAdded() * - itemChanged() * - itemRemoved() * Note that these three functions don't get the full payload of the items by default, * you need to change the item fetch scope of the change recorder to fetch the full * payload. This can be expensive with big payloads, though.
* Once you have handled changes in these methods call changeCommitted(). * These methods are called whenever a local item related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * *
Handling Collections
* * To follow collection changes in the backend, the following steps are necessary: * - Implement retrieveCollections() to retrieve collections from the backend. * If the backend supports incremental collections updates, implementing * support for that is recommended to improve performance. * - Convert the collections of the backend to Akonadi collections. * This typically happens either in retrieveCollections() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Collection objects for every retrieved * collection. It's very important that every object has its remote identifier * and its parent remote identifier set. * - Call collectionsRetrieved() or collectionsRetrievedIncremental() respectively * with the collection objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any collection in the Akonadi storage manually. * * * To write local collection changes back to the backend, you need to re-implement * the following three methods: * - collectionAdded() * - collectionChanged() * - collectionRemoved() * Once you have handled changes in these methods call changeCommitted(). * These methods are called whenever a local collection related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * * @todo Convenience base class for collection-less resources */ // FIXME_API: API dox need to be updated for Observer approach (kevin) class AKONADI_EXPORT ResourceBase : public AgentBase { Q_OBJECT public: /** * Use this method in the main function of your resource * application to initialize your resource subclass. * This method also takes care of creating a KApplication * object and parsing command line arguments. * * @note In case the given class is also derived from AgentBase::Observer * it gets registered as its own observer (see AgentBase::Observer), e.g. * resourceInstance->registerObserver( resourceInstance ); * * @code * * class MyResource : public ResourceBase * { * ... * }; * * int main( int argc, char **argv ) * { * return ResourceBase::init( argc, argv ); * } * * @endcode */ template static int init( int argc, char **argv ) { const QString id = parseArguments( argc, argv ); KApplication app; T* r = new T( id ); // check if T also inherits AgentBase::Observer and // if it does, automatically register it on itself Observer *observer = dynamic_cast( r ); if ( observer != 0 ) r->registerObserver( observer ); return init( r ); } /** * This method is used to set the name of the resource. */ //FIXME_API: make sure location is renamed to this by resourcebase void setName( const QString &name ); /** * Returns the name of the resource. */ QString name() const; Q_SIGNALS: /** * This signal is emitted whenever the name of the resource has changed. * * @param name The new name of the resource. */ void nameChanged( const QString &name ); /** * Emitted when a full synchronization has been completed. */ void synchronized(); protected Q_SLOTS: /** * Retrieve the collection tree from the remote server and supply it via * collectionsRetrieved() or collectionsRetrievedIncremental(). * @see collectionsRetrieved(), collectionsRetrievedIncremental() */ virtual void retrieveCollections() = 0; /** * Retrieve all (new/changed) items in collection @p collection. * It is recommended to use incremental retrieval if the backend supports that * and provide the result by calling itemsRetrievedIncremental(). * If incremental retrieval is not possible, provide the full listing by calling * itemsRetrieved( const Item::List& ). * In any case, ensure that all items have a correctly set remote identifier * to allow synchronizing with items already existing locally. * In case you don't want to use the built-in item syncing code, store the retrieved * items manually and call itemsRetrieved() once you are done. * @param collection The collection whose items to retrieve. * @see itemsRetrieved( const Item::List& ), itemsRetrievedIncremental(), itemsRetrieved(), currentCollection() */ virtual void retrieveItems( const Akonadi::Collection &collection ) = 0; /** * Retrieve a single item from the backend. The item to retrieve is provided as @p item. * Add the requested payload parts and call itemRetrieved() when done. * @param item The empty item whose payload should be retrieved. Use this object when delivering * the result instead of creating a new item to ensure conflict detection will work. * @param parts The item parts that should be retrieved. * @return false if there is an immediate error when retrieving the item. * @see itemRetrieved() */ virtual bool retrieveItem( const Akonadi::Item &item, const QSet &parts ) = 0; protected: /** * Creates a base resource. * * @param id The instance id of the resource. */ ResourceBase( const QString & id ); /** * Destroys the base resource. */ ~ResourceBase(); /** * Call this method from retrieveItem() once the result is available. * * @param item The retrieved item. */ void itemRetrieved( const Item &item ); /** * Resets the dirty flag of the given item and updates the remote id. * * Call whenever you have successfully written changes back to the server. * This implicitly calls changeProcessed(). * @param item The changed item. */ void changeCommitted( const Item &item ); /** * Call whenever you have successfully handled or ignored a collection * change notification. * * This will update the remote identifier of @p collection if necessary, * as well as any other collection attributes. * This implicitly calls changeProcessed(). * @param collection The collection which changes have been handled. */ void changeCommitted( const Collection &collection ); /** * Call this to supply the full folder tree retrieved from the remote server. * * @param collections A list of collections. * @see collectionsRetrievedIncremental() */ void collectionsRetrieved( const Collection::List &collections ); /** * Call this to supply incrementally retrieved collections from the remote server. * * @param changedCollections Collections that have been added or changed. * @param removedCollections Collections that have been deleted. * @see collectionsRetrieved() */ void collectionsRetrievedIncremental( const Collection::List &changedCollections, const Collection::List &removedCollections ); + /** + * Enable collection streaming, that is collections don't have to be delivered at once + * as result of a retrieveCollections() call but can be delivered by multiple calls + * to collectionsRetrieved() or collectionsRetrievedIncremental(). When all collections + * have been retrieved, call collectionsRetrievalDone(). + * @param enable @c true if collection streaming should be enabled, @c false by default + */ + void setCollectionStreamingEnabled( bool enable ); + + /** + * Call this method to indicate you finished synchronizing the collection tree. + * + * This is not needed if you use the built in syncing without collection streaming + * and call collectionsRetrieved() or collectionRetrievedIncremental() instead. + * If collection streaming is enabled, call this method once all collections have been delivered + * using collectionsRetrieved() or collectionsRetrievedIncremental(). + */ + void collectionsRetrievalDone(); + /** * Call this method to supply the full collection listing from the remote server. * * If the remote server supports incremental listing, it's strongly * recommended to use itemsRetrievedIncremental() instead. * @param items A list of items. * @see itemsRetrievedIncremental(). */ void itemsRetrieved( const Item::List &items ); /** * Call this method when you want to use the itemsRetrieved() method * in streaming mode and indicate the amount of items that will arrive * that way. * @deprecated Use setItemStreamingEnabled( true ) + itemsRetrieved[Incremental]() * + itemsRetrieved() instead. */ void setTotalItems( int amount ); /** * Enable item streaming. * Item streaming is disabled by default. * @param enable @c true if items are delivered in chunks rather in one big block. */ void setItemStreamingEnabled( bool enable ); /** * Call this method to supply incrementally retrieved items from the remote server. * * @param changedItems Items changed in the backend. * @param removedItems Items removed from the backend. */ void itemsRetrievedIncremental( const Item::List &changedItems, const Item::List &removedItems ); /** * Call this method to indicate you finished synchronizing the current collection. * * This is not needed if you use the built in syncing without item streaming * and call itemsRetrieved() or itemsRetrievedIncremental() instead. * If item streaming is enabled, call this method once all items have been delivered * using itemsRetrieved() or itemsRetrievedIncremental(). * @see retrieveItems() */ void itemsRetrievalDone(); /** * Call this method to remove all items and collections of the resource from the * server cache. * * The method should be used whenever the configuration of the resource has changed * and therefor the cached items might not be valid any longer. * * @since 4.3 */ void clearCache(); /** * Returns the collection that is currently synchronized. */ Collection currentCollection() const; /** * Returns the item that is currently retrieved. */ Item currentItem() const; /** * This method is called whenever the resource should start synchronize all data. */ void synchronize(); /** * This method is called whenever the collection with the given @p id * shall be synchronized. */ void synchronizeCollection( qint64 id ); /** * Refetches the Collections. */ void synchronizeCollectionTree(); /** * Stops the execution of the current task and continues with the next one. */ void cancelTask(); /** * Stops the execution of the current task and continues with the next one. * Additionally an error message is emitted. */ void cancelTask( const QString &error ); /** * Stops the execution of the current task and continues with the next one. * The current task will be tried again later. * * @since 4.3 */ void deferTask(); /** * Inherited from AgentBase. */ void doSetOnline( bool online ); private: static QString parseArguments( int, char** ); static int init( ResourceBase *r ); // dbus resource interface friend class ::ResourceAdaptor; bool requestItemDelivery( qint64 uid, const QString &remoteId, const QString &mimeType, const QStringList &parts ); private: Q_DECLARE_PRIVATE( ResourceBase ) Q_PRIVATE_SLOT( d_func(), void slotDeliveryDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionSyncDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotDeleteResourceCollection() ) Q_PRIVATE_SLOT( d_func(), void slotDeleteResourceCollectionDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionDeletionDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotLocalListDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotSynchronizeCollection( const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionListDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotItemSyncDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotPercent( KJob*, unsigned long ) ) }; } #ifndef AKONADI_RESOURCE_MAIN /** * Convenience Macro for the most common main() function for Akonadi resources. */ #define AKONADI_RESOURCE_MAIN( resourceClass ) \ int main( int argc, char **argv ) \ { \ return Akonadi::ResourceBase::init( argc, argv ); \ } #endif #endif diff --git a/akonadi/resourcescheduler_p.h b/akonadi/resourcescheduler_p.h index 32cb6e33d..f870a6587 100644 --- a/akonadi/resourcescheduler_p.h +++ b/akonadi/resourcescheduler_p.h @@ -1,173 +1,173 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_RESOURCESCHEDULER_P_H #define AKONADI_RESOURCESCHEDULER_P_H #include #include #include #include #include #include namespace Akonadi { //@cond PRIVATE /** @internal Manages synchronization and fetch requests for a resource. @todo Attach to the ResourceBase Monitor, */ class ResourceScheduler : public QObject { Q_OBJECT public: enum TaskType { Invalid, SyncAll, SyncCollectionTree, SyncCollection, FetchItem, ChangeReplay, DeleteResourceCollection, SyncAllDone }; class Task { static qint64 latestSerial; public: Task() : serial( ++latestSerial ), type( Invalid ) {} qint64 serial; TaskType type; Collection collection; Item item; QSet itemParts; QDBusMessage dbusMsg; bool operator==( const Task &other ) const { return type == other.type - && collection == other.collection - && item == other.item + && (collection == other.collection || (!collection.isValid() && !other.collection.isValid())) + && (item == other.item || (!item.isValid() && !other.item.isValid())) && itemParts == other.itemParts; } }; ResourceScheduler( QObject *parent = 0 ); /** Schedules a full synchronization. */ void scheduleFullSync(); /** Schedules a collection tree sync. */ void scheduleCollectionTreeSync(); /** Schedules the synchronization of a single collection. @param col The collection to synchronize. */ void scheduleSync( const Collection &col ); /** Schedules fetching of a single PIM item. @param item The item to fetch. @param parts List of names of the parts of the item to fetch. @param msg The associated D-Bus message. */ void scheduleItemFetch( const Item &item, const QSet &parts, const QDBusMessage &msg ); /** Schedules deletion of the resource collection. This method is used to implement the ResourceBase::clearCache() functionality. */ void scheduleResourceCollectionDeletion(); /** Insert synchronization completetion marker into the task queue. */ void scheduleFullSyncCompletion(); /** Returns true if no tasks are running or in the queue. */ bool isEmpty(); /** Returns the current task. */ Task currentTask() const; /** Sets the online state. */ void setOnline( bool state ); public Q_SLOTS: /** Schedules replaying changes. */ void scheduleChangeReplay(); /** The current task has been finished */ void taskDone(); /** The current task can't be finished now and will be rescheduled later */ void deferTask(); Q_SIGNALS: void executeFullSync(); void executeCollectionSync( const Akonadi::Collection &col ); void executeCollectionTreeSync(); void executeItemFetch( const Akonadi::Item &item, const QSet &parts ); void executeResourceCollectionDeletion(); void executeChangeReplay(); void fullSyncComplete(); void status( int status, const QString &message = QString() ); private slots: void scheduleNext(); void executeNext(); private: void signalTaskToTracker( const Task &task, const QByteArray &taskType ); QList mTaskList; Task mCurrentTask; bool mOnline; }; //@endcond } #endif diff --git a/akonadi/selftestdialog_p.h b/akonadi/selftestdialog_p.h index 56125ded7..f550c9706 100644 --- a/akonadi/selftestdialog_p.h +++ b/akonadi/selftestdialog_p.h @@ -1,83 +1,98 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SELFTESTDIALOG_P_H #define AKONADI_SELFTESTDIALOG_P_H #include "akonadiprivate_export.h" #include "ui_selftestdialog.h" #include #include class QStandardItem; class QStandardItemModel; namespace Akonadi { /** * @internal + * + * @short A dialog that checks the current status of the Akonadi system. + * + * This dialog checks the current status of the Akonadi system and + * displays a summary of the checks. + * + * @author Volker Krause */ class AKONADI_TESTS_EXPORT SelfTestDialog : public KDialog { Q_OBJECT public: + /** + * Creates a new self test dialog. + * + * @param parent The parent widget. + */ SelfTestDialog( QWidget *parent = 0 ); + + /** + * Hides the label with the introduction message. + */ void hideIntroduction(); + private slots: + void selectionChanged( const QModelIndex &index ); + void saveReport(); + void copyReport(); + void linkActivated( const QString &link ); + private: enum ResultType { Skip, Success, Warning, Error }; QStandardItem* report( ResultType type, const KLocalizedString &summary, const KLocalizedString &details ); void runTests(); QVariant serverSetting( const QString &group, const char *key, const QVariant &def ) const; bool useStandaloneMysqlServer() const; bool runProcess( const QString &app, const QStringList &args, QString &result ) const; void testSQLDriver(); void testMySQLServer(); void testMySQLServerLog(); void testMySQLServerConfig(); void testAkonadiCtl(); void testServerStatus(); void testProtocolVersion(); void testResources(); void testServerLog(); void testControlLog(); QString createReport(); - private slots: - void selectionChanged( const QModelIndex &index ); - void saveReport(); - void copyReport(); - void linkActivated( const QString &link ); - - private: Ui::SelfTestDialog ui; QStandardItemModel* mTestModel; }; } #endif diff --git a/akonadi/standardactionmanager.cpp b/akonadi/standardactionmanager.cpp index b51e55990..0d96b7770 100644 --- a/akonadi/standardactionmanager.cpp +++ b/akonadi/standardactionmanager.cpp @@ -1,419 +1,420 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "standardactionmanager.h" #include "agentmanager.h" #include "collectioncreatejob.h" #include "collectiondeletejob.h" #include "collectionmodel.h" #include "collectionutils_p.h" #include "collectionpropertiesdialog.h" #include "itemdeletejob.h" #include "itemmodel.h" #include "pastehelper_p.h" #include "subscriptiondialog_p.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; //@cond PRIVATE static const struct { const char *name; const char *label; const char *icon; int shortcut; const char* slot; } actionData[] = { { "akonadi_collection_create", I18N_NOOP("&New Folder..."), "folder-new", 0, SLOT(slotCreateCollection()) }, { "akonadi_collection_copy", 0, "edit-copy", 0, SLOT(slotCopyCollections()) }, { "akonadi_collection_delete", I18N_NOOP("&Delete Folder"), "edit-delete", 0, SLOT(slotDeleteCollection()) }, { "akonadi_collection_sync", I18N_NOOP("&Synchronize Folder"), "view-refresh", Qt::Key_F5, SLOT(slotSynchronizeCollection()) }, { "akonadi_collection_properties", I18N_NOOP("Folder &Properties"), "configure", 0, SLOT(slotCollectionProperties()) }, { "akonadi_item_copy", 0, "edit-copy", 0, SLOT(slotCopyItems()) }, { "akonadi_paste", I18N_NOOP("&Paste"), "edit-paste", Qt::CTRL + Qt::Key_V, SLOT(slotPaste()) }, { "akonadi_item_delete", 0, "edit-delete", Qt::Key_Delete, SLOT(slotDeleteItems()) }, { "akonadi_manage_local_subscriptions", I18N_NOOP("Manage Local &Subscriptions..."), 0, 0, SLOT(slotLocalSubscription()) } }; static const int numActionData = sizeof actionData / sizeof *actionData; BOOST_STATIC_ASSERT( numActionData == StandardActionManager::LastType ); static bool canCreateCollection( const Collection &collection ) { if ( !( collection.rights() & Collection::CanCreateCollection ) ) return false; if ( !collection.contentMimeTypes().contains( Collection::mimeType() ) ) return false; return true; } /** * @internal */ class StandardActionManager::Private { public: Private( StandardActionManager *parent ) : q( parent ), collectionSelectionModel( 0 ), itemSelectionModel( 0 ) { actions.fill( 0, StandardActionManager::LastType ); pluralLabels.insert( StandardActionManager::CopyCollections, ki18np( "&Copy Folder", "&Copy %1 Folders" ) ); pluralLabels.insert( StandardActionManager::CopyItems, ki18np( "&Copy Item", "&Copy %1 Items" ) ); pluralLabels.insert( StandardActionManager::DeleteItems, ki18np( "&Delete Item", "&Delete %1 Items" ) ); } void enableAction( StandardActionManager::Type type, bool enable ) { Q_ASSERT( type >= 0 && type < StandardActionManager::LastType ); if ( actions[type] ) actions[type]->setEnabled( enable ); } void updatePluralLabel( StandardActionManager::Type type, int count ) { Q_ASSERT( type >= 0 && type < StandardActionManager::LastType ); if ( actions[type] && pluralLabels.contains( type ) && !pluralLabels.value( type ).isEmpty() ) { actions[type]->setText( pluralLabels.value( type ).subs( qMax( count, 1 ) ).toString() ); } } void copy( QItemSelectionModel* selModel ) { Q_ASSERT( selModel ); if ( selModel->selectedRows().count() <= 0 ) return; QMimeData *mimeData = selModel->model()->mimeData( selModel->selectedRows() ); QApplication::clipboard()->setMimeData( mimeData ); } void updateActions() { bool singleColSelected = false; bool multiColSelected = false; int colCount = 0; QModelIndex selectedIndex; if ( collectionSelectionModel ) { colCount = collectionSelectionModel->selectedRows().count(); singleColSelected = colCount == 1; multiColSelected = colCount > 0; if ( singleColSelected ) selectedIndex = collectionSelectionModel->selectedRows().first(); } enableAction( CopyCollections, multiColSelected ); enableAction( CollectionProperties, singleColSelected ); Collection col; if ( singleColSelected && selectedIndex.isValid() ) { col = selectedIndex.data( CollectionModel::CollectionRole ).value(); enableAction( CreateCollection, canCreateCollection( col ) ); enableAction( DeleteCollections, col.rights() & Collection::CanDeleteCollection ); enableAction( SynchronizeCollections, CollectionUtils::isResource( col ) || CollectionUtils::isFolder( col ) ); enableAction( Paste, PasteHelper::canPaste( QApplication::clipboard()->mimeData(), col ) ); } else { enableAction( CreateCollection, false ); enableAction( DeleteCollections, false ); enableAction( SynchronizeCollections, false ); enableAction( Paste, false ); } bool multiItemSelected = false; int itemCount = 0; if ( itemSelectionModel ) { itemCount = itemSelectionModel->selectedRows().count(); multiItemSelected = itemCount > 0; } enableAction( CopyItems, multiItemSelected ); const bool canDeleteItem = !col.isValid() || (col.rights() & Collection::CanDeleteItem); enableAction( DeleteItems, multiItemSelected && canDeleteItem ); updatePluralLabel( CopyCollections, colCount ); updatePluralLabel( CopyItems, itemCount ); updatePluralLabel( DeleteItems, itemCount ); emit q->actionStateUpdated(); } void clipboardChanged( QClipboard::Mode mode ) { if ( mode == QClipboard::Clipboard ) updateActions(); } void slotCreateCollection() { Q_ASSERT( collectionSelectionModel ); if ( collectionSelectionModel->selection().indexes().isEmpty() ) return; const QModelIndex index = collectionSelectionModel->selection().indexes().at( 0 ); Q_ASSERT( index.isValid() ); const Collection collection = index.data( CollectionModel::CollectionRole ).value(); Q_ASSERT( collection.isValid() ); if ( !canCreateCollection( collection ) ) return; const QString name = KInputDialog::getText( i18nc( "@title:window", "New Folder"), i18nc( "@label:textbox, name of a thing", "Name"), QString(), 0, parentWidget ); if ( name.isEmpty() ) return; Collection::Id parentId = index.data( CollectionModel::CollectionIdRole ).toLongLong(); if ( parentId <= 0 ) return; Collection col; col.setName( name ); col.setParent( parentId ); CollectionCreateJob *job = new CollectionCreateJob( col ); q->connect( job, SIGNAL(result(KJob*)), q, SLOT(collectionCreationResult(KJob*)) ); } void slotCopyCollections() { copy( collectionSelectionModel ); } void slotDeleteCollection() { Q_ASSERT( collectionSelectionModel ); if ( collectionSelectionModel->selection().indexes().isEmpty() ) return; const QModelIndex index = collectionSelectionModel->selection().indexes().at( 0 ); Q_ASSERT( index.isValid() ); const Collection collection = index.data( CollectionModel::CollectionRole ).value(); Q_ASSERT( collection.isValid() ); QString text = i18n( "Do you really want to delete folder '%1' and all its sub-folders?", index.data().toString() ); if ( CollectionUtils::isVirtual( collection ) ) text = i18n( "Do you really want to delete the search view '%1'?", index.data().toString() ); if ( KMessageBox::questionYesNo( parentWidget, text, i18n("Delete folder?"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous ) != KMessageBox::Yes ) return; const Collection::Id colId = index.data( CollectionModel::CollectionIdRole ).toLongLong(); if ( colId <= 0 ) return; CollectionDeleteJob *job = new CollectionDeleteJob( Collection( colId ), q ); q->connect( job, SIGNAL(result(KJob*)), q, SLOT(collectionDeletionResult(KJob*)) ); } void slotSynchronizeCollection() { Q_ASSERT( collectionSelectionModel ); if ( collectionSelectionModel->selection().indexes().isEmpty() ) return; const QModelIndex index = collectionSelectionModel->selection().indexes().at( 0 ); Q_ASSERT( index.isValid() ); const Collection col = index.data( CollectionModel::CollectionRole ).value(); Q_ASSERT( col.isValid() ); AgentManager::self()->synchronizeCollection( col ); } void slotCollectionProperties() { if ( collectionSelectionModel->selection().indexes().isEmpty() ) return; const QModelIndex index = collectionSelectionModel->selection().indexes().at( 0 ); Q_ASSERT( index.isValid() ); Collection col = index.data( CollectionModel::CollectionRole ).value(); Q_ASSERT( col.isValid() ); CollectionPropertiesDialog* dlg = new CollectionPropertiesDialog( col, parentWidget ); dlg->show(); } void slotCopyItems() { copy( itemSelectionModel ); } void slotPaste() { Q_ASSERT( collectionSelectionModel ); if ( collectionSelectionModel->selection().indexes().isEmpty() ) return; const QModelIndex index = collectionSelectionModel->selection().indexes().at( 0 ); Q_ASSERT( index.isValid() ); const Collection col = index.data( CollectionModel::CollectionRole ).value(); Q_ASSERT( col.isValid() ); KJob *job = PasteHelper::paste( QApplication::clipboard()->mimeData(), col ); q->connect( job, SIGNAL(result(KJob*)), q, SLOT(pasteResult(KJob*)) ); } void slotDeleteItems() { if ( KMessageBox::questionYesNo( parentWidget, i18n( "Do you really want to delete all selected items?" ), i18n("Delete?"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous ) != KMessageBox::Yes ) return; Q_ASSERT( itemSelectionModel ); // TODO: fix this once ItemModifyJob can handle item lists foreach ( const QModelIndex &index, itemSelectionModel->selectedRows() ) { bool ok; qlonglong id = index.data( ItemModel::IdRole ).toLongLong(&ok); Q_ASSERT(ok); new ItemDeleteJob( Item( id ), q ); } } void slotLocalSubscription() { SubscriptionDialog* dlg = new SubscriptionDialog( parentWidget ); dlg->show(); } void collectionCreationResult( KJob *job ) { if ( job->error() ) { KMessageBox::error( parentWidget, i18n("Could not create folder: %1", job->errorString()), i18n("Folder creation failed") ); } } void collectionDeletionResult( KJob *job ) { if ( job->error() ) { KMessageBox::error( parentWidget, i18n("Could not delete folder: %1", job->errorString()), i18n("Folder deletion failed") ); } } void pasteResult( KJob *job ) { if ( job->error() ) { KMessageBox::error( parentWidget, i18n("Could not paste data: %1", job->errorString()), i18n("Paste failed") ); } } StandardActionManager *q; KActionCollection *actionCollection; QWidget *parentWidget; QItemSelectionModel *collectionSelectionModel; QItemSelectionModel *itemSelectionModel; QVector actions; AgentManager *agentManager; QHash pluralLabels; }; //@endcond StandardActionManager::StandardActionManager( KActionCollection * actionCollection, QWidget * parent) : QObject( parent ), d( new Private( this ) ) { d->parentWidget = parent; d->actionCollection = actionCollection; connect( QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), SLOT(clipboardChanged(QClipboard::Mode)) ); } StandardActionManager::~ StandardActionManager() { delete d; } void StandardActionManager::setCollectionSelectionModel(QItemSelectionModel * selectionModel) { d->collectionSelectionModel = selectionModel; connect( selectionModel, SIGNAL(selectionChanged( const QItemSelection&, const QItemSelection& )), SLOT(updateActions()) ); } void StandardActionManager::setItemSelectionModel(QItemSelectionModel * selectionModel) { d->itemSelectionModel = selectionModel; connect( selectionModel, SIGNAL(selectionChanged( const QItemSelection&, const QItemSelection& )), SLOT(updateActions()) ); } KAction* StandardActionManager::createAction( Type type ) { Q_ASSERT( type >= 0 && type < LastType ); Q_ASSERT( actionData[type].name ); if ( d->actions[type] ) return d->actions[type]; KAction *action = new KAction( d->parentWidget ); if ( d->pluralLabels.contains( type ) && !d->pluralLabels.value( type ).isEmpty() ) action->setText( d->pluralLabels.value( type ).subs( 1 ).toString() ); else if ( actionData[type].label ) action->setText( i18n( actionData[type].label ) ); if ( actionData[type].icon ) action->setIcon( KIcon( QString::fromLatin1( actionData[type].icon ) ) ); action->setShortcut( actionData[type].shortcut ); if ( actionData[type].slot ) connect( action, SIGNAL(triggered()), actionData[type].slot ); d->actionCollection->addAction( QString::fromLatin1(actionData[type].name), action ); d->actions[type] = action; d->updateActions(); return action; } void StandardActionManager::createAllActions() { for ( int i = 0; i < LastType; ++i ) createAction( (Type)i ); } KAction * StandardActionManager::action( Type type ) const { Q_ASSERT( type >= 0 && type < LastType ); return d->actions[type]; } void StandardActionManager::setActionText(Type type, const KLocalizedString & text) { Q_ASSERT( type >= 0 && type < LastType ); d->pluralLabels.insert( type, text ); + d->updateActions(); } #include "standardactionmanager.moc" diff --git a/akonadi/tests/CMakeLists.txt b/akonadi/tests/CMakeLists.txt index d857f09f6..f4f128bd9 100644 --- a/akonadi/tests/CMakeLists.txt +++ b/akonadi/tests/CMakeLists.txt @@ -1,118 +1,123 @@ if(${EXECUTABLE_OUTPUT_PATH}) set( PREVIOUS_EXEC_OUTPUT_PATH ${EXECUTABLE_OUTPUT_PATH} ) else(${EXECUTABLE_OUTPUT_PATH}) set( PREVIOUS_EXEC_OUTPUT_PATH . ) endif(${EXECUTABLE_OUTPUT_PATH}) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) include_directories( ${CMAKE_SOURCE_DIR}/akonadi ${CMAKE_CURRENT_SOURCE_DIR}/../ ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/../ ${Boost_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR} ) # add testrunner (application for managing a self-contained test # environment) add_subdirectory(testrunner) # add benchmarker add_subdirectory(benchmarker) # convenience macro to add akonadi demo application macro(add_akonadi_demo _source) set(_test ${_source}) get_filename_component(_name ${_source} NAME_WE) kde4_add_executable(${_name} TEST ${_test}) target_link_libraries(${_name} akonadi-kde akonadi-kmime ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBS}) endmacro(add_akonadi_demo) # convenience macro to add akonadi qtestlib unit-tests macro(add_akonadi_test _source) set(_test ${_source}) get_filename_component(_name ${_source} NAME_WE) kde4_add_unit_test(${_name} TESTNAME akonadi-${_name} ${_test}) target_link_libraries(${_name} akonadi-kde akonadi-kmime ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${AKONADI_COMMON_LIBRARIES}) endmacro(add_akonadi_test) # convenience macro to add akonadi testrunner unit-tests macro(add_akonadi_isolated_test _source) set(_test ${_source}) get_filename_component(_name ${_source} NAME_WE) kde4_add_executable(${_name} TEST ${_test}) target_link_libraries(${_name} akonadi-kde akonadi-kmime ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${AKONADI_COMMON_LIBRARIES}) # based on kde4_add_unit_test if (WIN32) get_target_property( _loc ${_name} LOCATION ) set(_executable ${_loc}.bat) set(_testrunner ${PREVIOUS_EXEC_OUTPUT_PATH}/akonaditest.bat) else (WIN32) set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_name}) set(_testrunner ${PREVIOUS_EXEC_OUTPUT_PATH}/akonaditest) endif (WIN32) if (UNIX) set(_executable ${_executable}.shell) set(_testrunner ${_testrunner}.shell) endif (UNIX) add_test( akonadi-db-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-db.xml ${_executable} ) add_test( akonadi-fs-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-fs.xml ${_executable} ) #add_test( akonadi-sqlite-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-sqlite.xml ${_executable} ) endmacro(add_akonadi_isolated_test) # demo applications add_akonadi_demo(itemdumper.cpp) add_akonadi_demo(subscriber.cpp) add_akonadi_demo(headfetcher.cpp) add_akonadi_demo(agentinstancewidgettest.cpp) add_akonadi_demo(agenttypewidgettest.cpp) add_akonadi_demo(pluginloadertest.cpp) add_akonadi_demo(selftester.cpp) kde4_add_executable( akonadi-firstrun TEST ../firstrun.cpp firstrunner.cpp ) target_link_libraries( akonadi-firstrun akonadi-kde ${KDE4_KDEUI_LIBS} ) # qtestlib unit tests add_akonadi_test(imapparsertest.cpp) add_akonadi_test(imapsettest.cpp) add_akonadi_test(itemhydratest.cpp) add_akonadi_test(itemtest.cpp) add_akonadi_test(itemserializertest.cpp) add_akonadi_test(mimetypecheckertest.cpp) add_akonadi_test(protocolhelpertest.cpp) +# qtestlib tests that need non-exported stuff from akonadi-kde +kde4_add_unit_test(resourceschedulertest TESTNAME akonadi-resourceschedulertest resourceschedulertest.cpp ../resourcescheduler.cpp) +target_link_libraries(resourceschedulertest akonadi-kde ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${AKONADI_COMMON_LIBRARIES}) + + # testrunner tests add_akonadi_isolated_test(testenvironmenttest.cpp) add_akonadi_isolated_test(autoincrementtest.cpp) add_akonadi_isolated_test(attributefactorytest.cpp) add_akonadi_isolated_test(collectionjobtest.cpp) add_akonadi_isolated_test(collectionpathresolvertest.cpp) add_akonadi_isolated_test(collectionattributetest.cpp) add_akonadi_isolated_test(itemfetchtest.cpp) add_akonadi_isolated_test(itemappendtest.cpp) add_akonadi_isolated_test(itemstoretest.cpp) add_akonadi_isolated_test(itemdeletetest.cpp) add_akonadi_isolated_test(monitortest.cpp) add_akonadi_isolated_test(searchjobtest.cpp) add_akonadi_isolated_test(changerecordertest.cpp) add_akonadi_isolated_test(resourcetest.cpp) add_akonadi_isolated_test(subscriptiontest.cpp) add_akonadi_isolated_test(transactiontest.cpp) add_akonadi_isolated_test(itemcopytest.cpp) add_akonadi_isolated_test(itemmovetest.cpp) add_akonadi_isolated_test(collectioncopytest.cpp) add_akonadi_isolated_test(collectionmovetest.cpp) add_akonadi_isolated_test(collectionsynctest.cpp) add_akonadi_isolated_test(itemsynctest.cpp) add_akonadi_isolated_test(linktest.cpp) add_akonadi_isolated_test(cachetest.cpp) add_akonadi_isolated_test(servermanagertest.cpp) add_akonadi_isolated_test(collectioncreator.cpp) add_akonadi_isolated_test(itembenchmark.cpp) diff --git a/akonadi/tests/changerecordertest.cpp b/akonadi/tests/changerecordertest.cpp index bf30d0395..34bb89503 100644 --- a/akonadi/tests/changerecordertest.cpp +++ b/akonadi/tests/changerecordertest.cpp @@ -1,117 +1,149 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include using namespace Akonadi; Q_DECLARE_METATYPE(QSet) class ChangeRecorderTest : public QObject { Q_OBJECT private: void triggerChange( int uid ) { Item item( uid ); item.setFlag( "random_flag" ); ItemModifyJob *job = new ItemModifyJob( item ); job->disableRevisionCheck(); QVERIFY( job->exec() ); item.clearFlag( "random_flag" ); job = new ItemModifyJob( item ); job->disableRevisionCheck(); QVERIFY( job->exec() ); } private slots: void initTestCase() { qRegisterMetaType(); qRegisterMetaType >(); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } void testChangeRecorder() { QSettings *settings = new QSettings( "kde.org", "akonadi-changerecordertest", this ); settings->clear(); ChangeRecorder *rec = new ChangeRecorder(); rec->setConfig( settings ); rec->setAllMonitored(); QSignalSpy spy( rec, SIGNAL(itemChanged(Akonadi::Item,QSet)) ); QVERIFY( spy.isValid() ); QSignalSpy cspy( rec, SIGNAL(changesAdded()) ); QVERIFY( cspy.isValid() ); triggerChange( 1 ); triggerChange( 1 ); triggerChange( 3 ); QTest::qWait( 1000 ); // enter event loop and wait for change notifications from the server QCOMPARE( spy.count(), 0 ); QVERIFY( !cspy.isEmpty() ); delete rec; rec = new ChangeRecorder(); rec->setConfig( settings ); rec->setAllMonitored(); rec->itemFetchScope().fetchFullPayload(); rec->itemFetchScope().fetchAllAttributes(); QVERIFY( !rec->isEmpty() ); QSignalSpy spy2( rec, SIGNAL(itemChanged(Akonadi::Item,QSet)) ); QVERIFY( spy2.isValid() ); rec->replayNext(); rec->changeProcessed(); QVERIFY( !rec->isEmpty() ); QTest::qWait( 1000 ); QCOMPARE( spy2.count(), 1 ); rec->replayNext(); rec->changeProcessed(); QVERIFY( rec->isEmpty() ); QTest::qWait( 1000 ); QCOMPARE( spy2.count(), 2 ); rec->replayNext(); rec->changeProcessed(); QVERIFY( rec->isEmpty() ); QTest::qWait( 1000 ); QCOMPARE( spy2.count(), 2 ); delete rec; } + + void testEmptyChangeReplay() + { + ChangeRecorder recorder; + recorder.setAllMonitored(); + recorder.itemFetchScope().fetchFullPayload(); + recorder.itemFetchScope().fetchAllAttributes(); + QSignalSpy nothingSpy( &recorder, SIGNAL(nothingToReplay()) ); + QSignalSpy changedSpy( &recorder, SIGNAL(itemChanged(Akonadi::Item,QSet)) ); + QVERIFY( nothingSpy.isValid() ); + QVERIFY( changedSpy.isValid() ); + + // Nothing to replay, should emit that signal then. + recorder.replayNext(); + QCOMPARE( nothingSpy.count(), 1 ); + QCOMPARE( changedSpy.count(), 0 ); + + // Give it something to replay + triggerChange( 1 ); + QTest::qWait( 1000 ); // enter event loop and wait for change notifications from the server + recorder.replayNext(); + QTest::qWait( 1000 ); + QCOMPARE( nothingSpy.count(), 1 ); + QCOMPARE( changedSpy.count(), 1 ); + + // Nothing else to replay now + recorder.changeProcessed(); + recorder.replayNext(); + QTest::qWait( 1000 ); + QCOMPARE( nothingSpy.count(), 2 ); + QCOMPARE( changedSpy.count(), 1 ); + }; }; QTEST_AKONADIMAIN( ChangeRecorderTest, NoGUI ) #include "changerecordertest.moc" diff --git a/akonadi/tests/itemappendtest.cpp b/akonadi/tests/itemappendtest.cpp index bb6126f1b..d2b8b3ef2 100644 --- a/akonadi/tests/itemappendtest.cpp +++ b/akonadi/tests/itemappendtest.cpp @@ -1,204 +1,238 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "control.h" #include "itemappendtest.h" #include "testattribute.h" #include "test_utils.h" #include #include #include #include #include #include #include using namespace Akonadi; QTEST_AKONADIMAIN( ItemAppendTest, NoGUI ) void ItemAppendTest::initTestCase() { Control::start(); } void ItemAppendTest::testItemAppend_data() { QTest::addColumn( "remoteId" ); QTest::newRow( "empty" ) << QString(); QTest::newRow( "non empty" ) << QString( "remote-id" ); QTest::newRow( "whitespace" ) << QString( "remote id" ); QTest::newRow( "quotes" ) << QString ( "\"remote\" id" ); } void ItemAppendTest::testItemAppend() { const Collection testFolder1( collectionIdFromPath( "res2/space folder" ) ); QVERIFY( testFolder1.isValid() ); QFETCH( QString, remoteId ); Item ref; // for cleanup Item item( -1 ); item.setRemoteId( remoteId ); item.setMimeType( "application/octet-stream" ); item.setFlag( "TestFlag" ); item.setSize( 3456 ); ItemCreateJob *job = new ItemCreateJob( item, Collection( testFolder1 ), this ); - QVERIFY( job->exec() ); + AKVERIFYEXEC( job ); ref = job->item(); ItemFetchJob *fjob = new ItemFetchJob( testFolder1, this ); - QVERIFY( fjob->exec() ); + AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); QCOMPARE( fjob->items()[0], ref ); QCOMPARE( fjob->items()[0].remoteId(), remoteId ); QVERIFY( fjob->items()[0].flags().contains( "TestFlag" ) ); qint64 size = 3456; QCOMPARE( fjob->items()[0].size(), size ); ItemDeleteJob *djob = new ItemDeleteJob( ref, this ); - QVERIFY( djob->exec() ); + AKVERIFYEXEC( djob ); fjob = new ItemFetchJob( testFolder1, this ); - QVERIFY( fjob->exec() ); + AKVERIFYEXEC( fjob ); QVERIFY( fjob->items().isEmpty() ); } void ItemAppendTest::testContent_data() { QTest::addColumn( "data" ); QTest::newRow( "null" ) << QByteArray(); QTest::newRow( "empty" ) << QByteArray( "" ); QTest::newRow( "nullbyte" ) << QByteArray( "\0", 1 ); QTest::newRow( "nullbyte2" ) << QByteArray( "\0X", 2 ); QString utf8string = QString::fromUtf8("äöüß@€µøđ¢©®"); QTest::newRow( "utf8" ) << utf8string.toUtf8(); QTest::newRow( "newlines" ) << QByteArray("\nsome\n\nbreaked\ncontent\n\n"); QByteArray b; QTest::newRow( "big" ) << b.fill( 'a', 1 << 20 ); QTest::newRow( "bignull" ) << b.fill( '\0', 1 << 20 ); QTest::newRow( "bigcr" ) << b.fill( '\r', 1 << 20 ); QTest::newRow( "biglf" ) << b.fill( '\n', 1 << 20 ); } void ItemAppendTest::testContent() { const Collection testFolder1( collectionIdFromPath( "res2/space folder" ) ); QVERIFY( testFolder1.isValid() ); QFETCH( QByteArray, data ); Item item; item.setMimeType( "application/octet-stream" ); item.setPayload( data ); ItemCreateJob* job = new ItemCreateJob( item, testFolder1, this ); - QVERIFY( job->exec() ); + AKVERIFYEXEC( job ); Item ref = job->item(); ItemFetchJob *fjob = new ItemFetchJob( testFolder1, this ); fjob->fetchScope().fetchFullPayload(); - QVERIFY( fjob->exec() ); + AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); Item item2 = fjob->items().first(); QCOMPARE( item2.payload(), data ); QEXPECT_FAIL( "null", "Serializer cannot distinguish null vs. empty", Continue ); QCOMPARE( item2.payload().isNull(), data.isNull() ); ItemDeleteJob *djob = new ItemDeleteJob( ref, this ); - QVERIFY( djob->exec() ); + AKVERIFYEXEC( djob ); } void ItemAppendTest::testNewMimetype() { const Collection col( collectionIdFromPath( "res2/space folder" ) ); QVERIFY( col.isValid() ); Item item; item.setMimeType( "application/new-type" ); ItemCreateJob *job = new ItemCreateJob( item, col, this ); - QVERIFY( job->exec() ); + AKVERIFYEXEC( job ); item = job->item(); QVERIFY( item.isValid() ); ItemFetchJob *fetch = new ItemFetchJob( item, this ); - QVERIFY( fetch->exec() ); + AKVERIFYEXEC( fetch ); QCOMPARE( fetch->items().count(), 1 ); QCOMPARE( fetch->items().first().mimeType(), item.mimeType() ); } void ItemAppendTest::testIllegalAppend() { const Collection testFolder1( collectionIdFromPath( "res2/space folder" ) ); QVERIFY( testFolder1.isValid() ); Item item; item.setMimeType( "application/octet-stream" ); // adding item to non-existing collection ItemCreateJob *job = new ItemCreateJob( item, Collection( INT_MAX ), this ); QVERIFY( !job->exec() ); // adding item into a collection which can't handle items of this type const Collection col( collectionIdFromPath( "res1/foo/bla" ) ); QVERIFY( col.isValid() ); job = new ItemCreateJob( item, col, this ); QEXPECT_FAIL( "", "Test not yet implemented in the server.", Continue ); QVERIFY( !job->exec() ); } void ItemAppendTest::testMultipartAppend() { AttributeFactory::registerAttribute(); const Collection testFolder1( collectionIdFromPath( "res2/space folder" ) ); QVERIFY( testFolder1.isValid() ); Item item; item.setMimeType( "application/octet-stream" ); item.setPayload( "body data" ); item.attribute( Item::AddIfMissing )->data = "extra data"; item.setFlag( "TestFlag" ); ItemCreateJob *job = new ItemCreateJob( item, testFolder1, this ); - QVERIFY( job->exec() ); + AKVERIFYEXEC( job ); Item ref = job->item(); ItemFetchJob *fjob = new ItemFetchJob( ref, this ); fjob->fetchScope().fetchFullPayload(); fjob->fetchScope().fetchAttribute(); - QVERIFY( fjob->exec() ); + AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items().first(); QCOMPARE( item.payload(), QByteArray( "body data" ) ); QVERIFY( item.hasAttribute() ); QCOMPARE( item.attribute()->data, QByteArray( "extra data" ) ); QVERIFY( item.flags().contains( "TestFlag" ) ); ItemDeleteJob *djob = new ItemDeleteJob( ref, this ); - QVERIFY( djob->exec() ); + AKVERIFYEXEC( djob ); +} + +void ItemAppendTest::testItemSize_data() +{ + QTest::addColumn( "item" ); + QTest::addColumn( "size" ); + + Item i( "application/octet-stream" ); + i.setPayload( QByteArray( "ABCD" ) ); + + QTest::newRow( "auto size" ) << i << 4ll; + i.setSize( 3 ); + QTest::newRow( "too small" ) << i << 4ll; + i.setSize( 10 ); + QTest::newRow( "too large" ) << i << 10ll; +} + +void ItemAppendTest::testItemSize() +{ + QFETCH( Akonadi::Item, item ); + QFETCH( qint64, size ); + + const Collection col( collectionIdFromPath( "res2/space folder" ) ); + QVERIFY( col.isValid() ); + + ItemCreateJob *create = new ItemCreateJob( item, col, this ); + AKVERIFYEXEC( create ); + Item newItem = create->item(); + + ItemFetchJob *fetch = new ItemFetchJob( newItem, this ); + AKVERIFYEXEC( fetch ); + QCOMPARE( fetch->items().count(), 1 ); + + QCOMPARE( fetch->items().first().size(), size ); } #include "itemappendtest.moc" diff --git a/akonadi/tests/itemappendtest.h b/akonadi/tests/itemappendtest.h index 995572214..317b12092 100644 --- a/akonadi/tests/itemappendtest.h +++ b/akonadi/tests/itemappendtest.h @@ -1,40 +1,42 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ITEMAPPENDTEST_H #define ITEMAPPENDTEST_H #include class ItemAppendTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testItemAppend_data(); void testItemAppend(); void testContent_data(); void testContent(); void testNewMimetype(); void testIllegalAppend(); void testMultipartAppend(); + void testItemSize_data(); + void testItemSize(); }; #endif diff --git a/akonadi/tests/itemcopytest.cpp b/akonadi/tests/itemcopytest.cpp index 5a7956d85..fa6ecce96 100644 --- a/akonadi/tests/itemcopytest.cpp +++ b/akonadi/tests/itemcopytest.cpp @@ -1,89 +1,97 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "test_utils.h" #include using namespace Akonadi; class ItemCopyTest : public QObject { Q_OBJECT private slots: void initTestCase() { Control::start(); // switch target resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) { if ( agent.identifier() == "akonadi_knut_resource_2" ) agent.setIsOnline( false ); } } void testCopy() { const Collection target( collectionIdFromPath( "res3" ) ); QVERIFY( target.isValid() ); ItemCopyJob *copy = new ItemCopyJob( Item( 1 ), target ); - QVERIFY( copy->exec() ); + AKVERIFYEXEC( copy ); + + Item source( 1 ); + ItemFetchJob *sourceFetch = new ItemFetchJob( source ); + AKVERIFYEXEC( sourceFetch ); + source = sourceFetch->items().first(); ItemFetchJob *fetch = new ItemFetchJob( target ); fetch->fetchScope().fetchFullPayload(); fetch->fetchScope().fetchAllAttributes(); fetch->fetchScope().setCacheOnly( true ); - QVERIFY( fetch->exec() ); + AKVERIFYEXEC( fetch ); QCOMPARE( fetch->items().count(), 1 ); Item item = fetch->items().first(); QVERIFY( item.hasPayload() ); + QVERIFY( source.size() > 0 ); + QVERIFY( item.size() > 0 ); + QCOMPARE( item.size(), source.size() ); QCOMPARE( item.attributes().count(), 1 ); QVERIFY( item.remoteId().isEmpty() ); } void testIlleagalCopy() { // empty item list ItemCopyJob *copy = new ItemCopyJob( Item::List(), Collection::root() ); QVERIFY( !copy->exec() ); // non-existing target copy = new ItemCopyJob( Item( 1 ), Collection( INT_MAX ) ); QVERIFY( !copy->exec() ); // non-existing source copy = new ItemCopyJob( Item( INT_MAX ), Collection::root() ); QVERIFY( !copy->exec() ); } }; QTEST_AKONADIMAIN( ItemCopyTest, NoGUI ) #include "itemcopytest.moc" diff --git a/akonadi/tests/itemmovetest.cpp b/akonadi/tests/itemmovetest.cpp index 4f352e39e..1cfef65db 100644 --- a/akonadi/tests/itemmovetest.cpp +++ b/akonadi/tests/itemmovetest.cpp @@ -1,106 +1,106 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_utils.h" #include #include #include #include #include #include #include #include using namespace Akonadi; class ItemMoveTest: public QObject { Q_OBJECT private slots: void initTestCase() { Control::start(); } // TODO: test inter and intra resource moves void testMove_data() { QTest::addColumn( "items" ); QTest::newRow( "single uid" ) << (Item::List() << Item( 1 )); -// QTest::newRow( "two uid" ) << (Item::List() << Item( 2 ) << Item( 3 )); -// Item r1; r1.setRemoteId( "D" ); -// QTest::newRow( "single rid" ) << (Item::List() << r1); + QTest::newRow( "two uid" ) << (Item::List() << Item( 2 ) << Item( 3 )); + Item r1; r1.setRemoteId( "D" ); + QTest::newRow( "single rid" ) << (Item::List() << r1); } void testMove() { QFETCH( Item::List, items ); Collection destination( collectionIdFromPath( "res3" ) ); QVERIFY( destination.isValid() ); Collection source( collectionIdFromPath( "res1/foo" ) ); QVERIFY( source.isValid() ); ItemFetchJob *prefetchjob = new ItemFetchJob( destination, this ); AKVERIFYEXEC( prefetchjob ); int baseline = prefetchjob->items().size(); CollectionSelectJob *select = new CollectionSelectJob( source ); AKVERIFYEXEC( select ); // for rid based moves ItemMoveJob *move = new ItemMoveJob( items, destination, this ); AKVERIFYEXEC( move ); ItemFetchJob *fetch = new ItemFetchJob( destination, this ); fetch->fetchScope().fetchFullPayload(); AKVERIFYEXEC( fetch ); QCOMPARE( fetch->items().count(), items.count() + baseline ); foreach ( const Item& movedItem, fetch->items() ) { QVERIFY( movedItem.hasPayload() ); QVERIFY( !movedItem.payload().isEmpty() ); } } void testIllegalMove() { Collection col( collectionIdFromPath( "res2" ) ); QVERIFY( col.isValid() ); ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 1 ) ); QVERIFY( prefetchjob->exec() ); QCOMPARE( prefetchjob->items().count(), 1 ); Item item = prefetchjob->items()[0]; // move into invalid collection ItemMoveJob *store = new ItemMoveJob( item, Collection( INT_MAX ), this ); QVERIFY( !store->exec() ); // move item into folder that doesn't support its mimetype store = new ItemMoveJob( item, col, this ); QEXPECT_FAIL( "", "Check not yet implemented by the server.", Continue ); QVERIFY( !store->exec() ); } }; QTEST_AKONADIMAIN( ItemMoveTest, NoGUI ) #include "itemmovetest.moc" diff --git a/akonadi/tests/itemstoretest.cpp b/akonadi/tests/itemstoretest.cpp index 8d9a4b2e2..5d2fdb0d2 100644 --- a/akonadi/tests/itemstoretest.cpp +++ b/akonadi/tests/itemstoretest.cpp @@ -1,379 +1,382 @@ /* Copyright (c) 2006 Volker Krause Copyright (c) 2007 Robert Zwerus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "control.h" #include "itemstoretest.h" #include "testattribute.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "test_utils.h" using namespace Akonadi; QTEST_AKONADIMAIN( ItemStoreTest, NoGUI ) static Collection res1_foo; static Collection res2; static Collection res3; void ItemStoreTest::initTestCase() { Control::start(); AttributeFactory::registerAttribute(); // get the collections we run the tests on res1_foo = Collection( collectionIdFromPath( "res1/foo" ) ); QVERIFY( res1_foo.isValid() ); res2 = Collection( collectionIdFromPath( "res2" ) ); QVERIFY( res2.isValid() ); res3 = Collection( collectionIdFromPath( "res3" ) ); QVERIFY( res3.isValid() ); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } void ItemStoreTest::testFlagChange() { ItemFetchJob *fjob = new ItemFetchJob( Item( 1 ) ); AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); Item item = fjob->items()[0]; // add a flag Item::Flags origFlags = item.flags(); Item::Flags expectedFlags = origFlags; expectedFlags.insert( "added_test_flag_1" ); item.setFlag( "added_test_flag_1" ); ItemModifyJob *sjob = new ItemModifyJob( item, this ); AKVERIFYEXEC( sjob ); fjob = new ItemFetchJob( Item( 1 ) ); AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.flags().count(), expectedFlags.count() ); Item::Flags diff = expectedFlags - item.flags(); QVERIFY( diff.isEmpty() ); // set flags expectedFlags.insert( "added_test_flag_2" ); item.setFlags( expectedFlags ); sjob = new ItemModifyJob( item, this ); AKVERIFYEXEC( sjob ); fjob = new ItemFetchJob( Item( 1 ) ); AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.flags().count(), expectedFlags.count() ); diff = expectedFlags - item.flags(); QVERIFY( diff.isEmpty() ); // remove a flag item.clearFlag( "added_test_flag_1" ); item.clearFlag( "added_test_flag_2" ); sjob = new ItemModifyJob( item, this ); AKVERIFYEXEC( sjob ); fjob = new ItemFetchJob( Item( 1 ) ); AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.flags().count(), origFlags.count() ); diff = origFlags - item.flags(); QVERIFY( diff.isEmpty() ); } void ItemStoreTest::testDataChange_data() { QTest::addColumn( "data" ); QTest::newRow( "simple" ) << QByteArray( "testbody" ); QTest::newRow( "null" ) << QByteArray(); QTest::newRow( "empty" ) << QByteArray( "" ); QTest::newRow( "nullbyte" ) << QByteArray( "\0", 1 ); QTest::newRow( "nullbyte2" ) << QByteArray( "\0X", 2 ); QTest::newRow( "linebreaks" ) << QByteArray( "line1\nline2\n\rline3\rline4\r\n" ); QTest::newRow( "linebreaks2" ) << QByteArray( "line1\r\nline2\r\n\r\n" ); QTest::newRow( "linebreaks3" ) << QByteArray( "line1\nline2" ); QByteArray b; QTest::newRow( "big" ) << b.fill( 'a', 1 << 20 ); QTest::newRow( "bignull" ) << b.fill( '\0', 1 << 20 ); QTest::newRow( "bigcr" ) << b.fill( '\r', 1 << 20 ); QTest::newRow( "biglf" ) << b.fill( '\n', 1 << 20 ); } void ItemStoreTest::testDataChange() { QFETCH( QByteArray, data ); Item item; ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 1 ) ); - prefetchjob->exec(); + AKVERIFYEXEC( prefetchjob ); item = prefetchjob->items()[0]; item.setMimeType( "application/octet-stream" ); item.setPayload( data ); QCOMPARE( item.payload(), data ); // modify data ItemModifyJob *sjob = new ItemModifyJob( item ); AKVERIFYEXEC( sjob ); ItemFetchJob *fjob = new ItemFetchJob( Item( 1 ) ); fjob->fetchScope().fetchFullPayload(); AKVERIFYEXEC( fjob ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), data ); + QEXPECT_FAIL( "null", "STORE will not update item size on 0 sizes", Continue ); + QEXPECT_FAIL( "empty", "STORE will not update item size on 0 sizes", Continue ); + QCOMPARE( item.size(), static_cast( data.size() ) ); QEXPECT_FAIL( "null", "Serializer cannot distinguish null vs. empty", Continue ); QCOMPARE( item.payload().isNull(), data.isNull() ); } void ItemStoreTest::testRemoteId_data() { QTest::addColumn( "rid" ); QTest::addColumn( "exprid" ); QTest::newRow( "set" ) << QString( "A" ) << QString( "A" ); QTest::newRow( "no-change" ) << QString() << QString( "A" ); QTest::newRow( "clear" ) << QString( "" ) << QString( "" ); QTest::newRow( "reset" ) << QString( "A" ) << QString( "A" ); QTest::newRow( "utf8" ) << QString( "ä ö ü @" ) << QString( "ä ö ü @" ); } void ItemStoreTest::testRemoteId() { QFETCH( QString, rid ); QFETCH( QString, exprid ); // pretend to be a resource, we cannot change remote identifiers otherwise ResourceSelectJob *rsel = new ResourceSelectJob( "akonadi_knut_resource_0", this ); AKVERIFYEXEC( rsel ); ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 1 ) ); AKVERIFYEXEC( prefetchjob ); Item item = prefetchjob->items()[0]; item.setRemoteId( rid ); ItemModifyJob *store = new ItemModifyJob( item, this ); AKVERIFYEXEC( store ); ItemFetchJob *fetch = new ItemFetchJob( item, this ); AKVERIFYEXEC( fetch ); QCOMPARE( fetch->items().count(), 1 ); item = fetch->items().at( 0 ); QCOMPARE( item.remoteId().toUtf8(), exprid.toUtf8() ); // no longer pretend to be a resource rsel = new ResourceSelectJob( QString(), this ); AKVERIFYEXEC( rsel ); } void ItemStoreTest::testMultiPart() { ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 1 ) ); QVERIFY( prefetchjob->exec() ); QCOMPARE( prefetchjob->items().count(), 1 ); Item item = prefetchjob->items()[0]; item.setMimeType( "application/octet-stream" ); item.setPayload( "testmailbody" ); item.attribute( Item::AddIfMissing )->data = "extra"; // store item ItemModifyJob *sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); ItemFetchJob *fjob = new ItemFetchJob( Item( 1 ) ); fjob->fetchScope().fetchAttribute(); fjob->fetchScope().fetchFullPayload(); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), QByteArray("testmailbody") ); QVERIFY( item.hasAttribute() ); QCOMPARE( item.attribute()->data, QByteArray("extra") ); // clean up item.removeAttribute( "EXTRA" ); sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); } void ItemStoreTest::testPartRemove() { ItemFetchJob *prefetchjob = new ItemFetchJob( Item( 2 ) ); prefetchjob->exec(); Item item = prefetchjob->items()[0]; item.setMimeType( "application/octet-stream" ); item.attribute( Item::AddIfMissing )->data = "extra"; // store item ItemModifyJob *sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); // fetch item and its parts (should be RFC822, HEAD and EXTRA) ItemFetchJob *fjob = new ItemFetchJob( Item( 2 ) ); fjob->fetchScope().fetchFullPayload(); fjob->fetchScope().fetchAllAttributes(); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items()[0]; QCOMPARE( item.attributes().count(), 2 ); QVERIFY( item.hasAttribute() ); // remove a part item.removeAttribute(); sjob = new ItemModifyJob( item ); QVERIFY( sjob->exec() ); // fetch item again (should only have RFC822 and HEAD left) ItemFetchJob *fjob2 = new ItemFetchJob( Item( 2 ) ); fjob2->fetchScope().fetchFullPayload(); fjob2->fetchScope().fetchAllAttributes(); QVERIFY( fjob2->exec() ); QCOMPARE( fjob2->items().count(), 1 ); item = fjob2->items()[0]; QCOMPARE( item.attributes().count(), 1 ); QVERIFY( !item.hasAttribute() ); } void ItemStoreTest::testRevisionCheck() { // make sure we don't have any other collection selected // otherwise EXPUNGE doesn't work and will be triggered by // the following tests and mess up the monitor testing CollectionSelectJob *sel = new CollectionSelectJob( Collection::root(), this ); QVERIFY( sel->exec() ); // fetch same item twice Item ref( 2 ); ItemFetchJob *prefetchjob = new ItemFetchJob( ref ); QVERIFY( prefetchjob->exec() ); QCOMPARE( prefetchjob->items().count(), 1 ); Item item1 = prefetchjob->items()[0]; Item item2 = prefetchjob->items()[0]; // store first item unmodified ItemModifyJob *sjob = new ItemModifyJob( item1 ); QVERIFY( sjob->exec() ); // try to store second item ItemModifyJob *sjob2 = new ItemModifyJob( item2 ); item2.attribute( Item::AddIfMissing )->data = "extra"; QVERIFY( !sjob2->exec() ); // fetch same again prefetchjob = new ItemFetchJob( ref ); prefetchjob->exec(); item1 = prefetchjob->items()[0]; // delete item ItemDeleteJob *djob = new ItemDeleteJob( ref, this ); djob->exec(); // try to store it sjob = new ItemModifyJob( item1 ); QVERIFY( !sjob->exec() ); } void ItemStoreTest::testModificationTime() { Item item; item.setMimeType( "text/directory" ); QVERIFY( item.modificationTime().isNull() ); ItemCreateJob *job = new ItemCreateJob( item, res1_foo ); QVERIFY( job->exec() ); // The item should have a datetime set now. item = job->item(); QVERIFY( !item.modificationTime().isNull() ); QDateTime initialDateTime = item.modificationTime(); // Fetch the same item again. Item item2( item.id() ); ItemFetchJob *fjob = new ItemFetchJob( item2, this ); QVERIFY( fjob->exec() ); item2 = fjob->items().first(); QCOMPARE( initialDateTime, item2.modificationTime() ); // Lets wait 5 secs. QTest::qWait( 5000 ); // Modify the item item.attribute( Item::AddIfMissing )->data = "extra"; ItemModifyJob *mjob = new ItemModifyJob( item ); QVERIFY( mjob->exec() ); // The item should still have a datetime set and that date should be somewhere // after the initialDateTime. item = mjob->item(); QVERIFY( !item.modificationTime().isNull() ); QVERIFY( initialDateTime < item.modificationTime() ); // Fetch the item after modification. Item item3( item.id() ); ItemFetchJob *fjob2 = new ItemFetchJob( item3, this ); QVERIFY( fjob2->exec() ); // item3 should have the same modification time as item. item3 = fjob2->items().first(); QCOMPARE( item3.modificationTime(), item.modificationTime() ); // Clean up ItemDeleteJob *idjob = new ItemDeleteJob( item, this ); QVERIFY( idjob->exec() ); } void ItemStoreTest::testRemoteIdRace() { // Create an item and store it Item item; item.setMimeType( "text/directory" ); ItemCreateJob *job = new ItemCreateJob( item, res1_foo ); QVERIFY( job->exec() ); // Fetch the same item again. It should not have a remote Id yet, as the resource // is offline. // The remote id should be null, not only empty, so that item modify jobs with this // item don't overwrite the remote id. Item item2( job->item().id() ); ItemFetchJob *fetchJob = new ItemFetchJob( item2 ); QVERIFY( fetchJob->exec() ); QCOMPARE( fetchJob->items().size(), 1 ); QVERIFY( fetchJob->items().first().remoteId().isNull() ); } #include "itemstoretest.moc" diff --git a/akonadi/tests/resourceschedulertest.cpp b/akonadi/tests/resourceschedulertest.cpp new file mode 100644 index 000000000..7005c5a73 --- /dev/null +++ b/akonadi/tests/resourceschedulertest.cpp @@ -0,0 +1,121 @@ +/* + Copyright (c) 2009 Thomas McGuire + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +#include "resourceschedulertest.h" + +#include "../resourcescheduler_p.h" + +#include + +using namespace Akonadi; + +QTEST_KDEMAIN( ResourceSchedulerTest, NoGUI ) + +void ResourceSchedulerTest::testTaskComparision() +{ + ResourceScheduler::Task t1; + t1.type = ResourceScheduler::ChangeReplay; + ResourceScheduler::Task t2; + t2.type = ResourceScheduler::ChangeReplay; + QCOMPARE( t1, t2 ); + QList taskList; + taskList.append( t1 ); + QVERIFY( taskList.contains( t2 ) ); + + ResourceScheduler::Task t3; + t3.type = ResourceScheduler::DeleteResourceCollection; + QVERIFY( !( t2 == t3 ) ); + QVERIFY( !taskList.contains( t3 ) ); +} + +void ResourceSchedulerTest::testChangeReplaySchedule() +{ + ResourceScheduler scheduler; + scheduler.setOnline( true ); + qRegisterMetaType("Akonadi::Collection"); + QSignalSpy changeReplaySpy( &scheduler, SIGNAL( executeChangeReplay() ) ); + QSignalSpy collectionTreeSyncSpy( &scheduler, SIGNAL( executeCollectionTreeSync() ) ); + QSignalSpy syncSpy( &scheduler, SIGNAL( executeCollectionSync( const Akonadi::Collection & ) ) ); + QVERIFY( changeReplaySpy.isValid() ); + QVERIFY( collectionTreeSyncSpy.isValid() ); + QVERIFY( syncSpy.isValid() ); + + // Schedule a change replay, it should be executed first thing when we enter the + // event loop, but not before + QVERIFY( scheduler.isEmpty() ); + scheduler.scheduleChangeReplay(); + QVERIFY( !scheduler.isEmpty() ); + QVERIFY( changeReplaySpy.isEmpty() ); + QTest::qWait( 100 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + scheduler.taskDone(); + QTest::qWait( 100 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + + // Schedule two change replays. The duplicate one should not be executed. + changeReplaySpy.clear(); + scheduler.scheduleChangeReplay(); + scheduler.scheduleChangeReplay(); + QVERIFY( changeReplaySpy.isEmpty() ); + QTest::qWait( 100 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + scheduler.taskDone(); + QTest::qWait( 100 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + + // + // Schedule various stuff. + // + Collection collection( 42 ); + changeReplaySpy.clear(); + scheduler.scheduleCollectionTreeSync(); + scheduler.scheduleChangeReplay(); + scheduler.scheduleSync(collection); + scheduler.scheduleChangeReplay(); + + QTest::qWait( 100 ); + QCOMPARE( collectionTreeSyncSpy.count(), 1 ); + QCOMPARE( changeReplaySpy.count(), 0 ); + QCOMPARE( syncSpy.count(), 0 ); + + scheduler.taskDone(); + QTest::qWait( 100 ); + QCOMPARE( collectionTreeSyncSpy.count(), 1 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + QCOMPARE( syncSpy.count(), 0 ); + + // Omit a taskDone() here, there shouldn't be a new signal + QTest::qWait( 100 ); + QCOMPARE( collectionTreeSyncSpy.count(), 1 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + QCOMPARE( syncSpy.count(), 0 ); + + scheduler.taskDone(); + QTest::qWait( 100 ); + QCOMPARE( collectionTreeSyncSpy.count(), 1 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + QCOMPARE( syncSpy.count(), 1 ); + + // At this point, we're done, check that nothing else is emitted + scheduler.taskDone(); + QVERIFY( scheduler.isEmpty() ); + QTest::qWait( 100 ); + QCOMPARE( collectionTreeSyncSpy.count(), 1 ); + QCOMPARE( changeReplaySpy.count(), 1 ); + QCOMPARE( syncSpy.count(), 1 ); +} diff --git a/akonadi/tests/itemappendtest.h b/akonadi/tests/resourceschedulertest.h similarity index 69% copy from akonadi/tests/itemappendtest.h copy to akonadi/tests/resourceschedulertest.h index 995572214..51982b0a7 100644 --- a/akonadi/tests/itemappendtest.h +++ b/akonadi/tests/resourceschedulertest.h @@ -1,40 +1,33 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2009 Thomas McGuire This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - -#ifndef ITEMAPPENDTEST_H -#define ITEMAPPENDTEST_H +#ifndef RESOURCESCHEDULERTEST_H +#define RESOURCESCHEDULERTEST_H #include -class ItemAppendTest : public QObject +class ResourceSchedulerTest : public QObject { Q_OBJECT private Q_SLOTS: - void initTestCase(); - void testItemAppend_data(); - void testItemAppend(); - void testContent_data(); - void testContent(); - void testNewMimetype(); - void testIllegalAppend(); - void testMultipartAppend(); -}; + void testTaskComparision(); + void testChangeReplaySchedule(); +}; #endif diff --git a/akonadi/tests/testrunner/CMakeLists.txt b/akonadi/tests/testrunner/CMakeLists.txt index f40e2f8e0..e4ed024a6 100644 --- a/akonadi/tests/testrunner/CMakeLists.txt +++ b/akonadi/tests/testrunner/CMakeLists.txt @@ -1,47 +1,46 @@ project (akonaditest) include_directories( ${CMAKE_SOURCE_DIR}/kabc ${CMAKE_SOURCE_DIR}/kcal ${CMAKE_SOURCE_DIR}/akonadi ${CMAKE_BINARY_DIR}/kabc ${CMAKE_BINARY_DIR}/kcal ${CMAKE_BINARY_DIR}/akonadi ${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR} ${KDE4_INCLUDES} ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) set(akonaditest_SRCS main.cpp setup.cpp - configreader.cpp config.cpp shellscript.cpp symbols.cpp testrunner.cpp ) kde4_add_executable(akonaditest NOGUI ${akonaditest_SRCS}) target_link_libraries(akonaditest akonadi-kde kabc kcal syndication ${KDE4_KDEUI_LIBS} ${Boost_LIBRARIES} ${KDE4_AKONADI_LIBS} ${KDE4_KABC_LIBS} ${KDE4_KCAL_LIBS} ${KDE4_SYNDICATION_LIBS} ${KDE4_KDECORE_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTDBUS_LIBRARY} ) install(TARGETS akonaditest ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/akonadi/tests/testrunner/config.cpp b/akonadi/tests/testrunner/config.cpp index bf89c6dc9..de28b3eb1 100644 --- a/akonadi/tests/testrunner/config.cpp +++ b/akonadi/tests/testrunner/config.cpp @@ -1,102 +1,127 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" -#include "configreader.h" #include #include #include +#include +#include +#include +#include +#include -Config* Config::mInstance = 0; + +Q_GLOBAL_STATIC(Config, globalConfig) Config::Config() { } -Config *Config::instance( const QString& pathToConfig ) +Config::~Config() { - if ( mInstance == 0 ) { - QString cfgFile = pathToConfig; - if ( cfgFile.isEmpty() ) - cfgFile = KStandardDirs::locate( "config", "akonaditest.xml" ); +} - mInstance = new ConfigReader( cfgFile ); - } +Config *Config::instance(const QString &pathToConfig) +{ + if ( !pathToConfig.isEmpty() ) + globalConfig()->readConfiguration(pathToConfig); - return mInstance; + return globalConfig(); } - -void Config::destroyInstance() +void Config::readConfiguration(const QString &configfile) { - delete mInstance; + QDomDocument doc; + QFile file( configfile ); + + if ( !file.open( QIODevice::ReadOnly ) ) + qFatal( "error reading file: %s", qPrintable( configfile ) ); + + QString errorMsg; + if ( !doc.setContent( &file, &errorMsg ) ) + qFatal( "unable to parse config file: %s", qPrintable( errorMsg ) ); + + const QDomElement root = doc.documentElement(); + if ( root.tagName() != "config" ) + qFatal( "could not file root tag" ); + + const QString basePath = QFileInfo( configfile ).absolutePath() + "/"; + + QDomNode node = root.firstChild(); + while ( !node.isNull() ) { + const QDomElement element = node.toElement(); + if ( !element.isNull() ) { + if ( element.tagName() == "kdehome" ) { + setKdeHome( basePath + element.text() ); + } else if ( element.tagName() == "confighome" ) { + setXdgConfigHome( basePath + element.text() ); + } else if ( element.tagName() == "datahome" ) { + setXdgDataHome( basePath + element.text() ); + } else if ( element.tagName() == "agent" ) { + insertAgent( element.text(), element.attribute( "synchronize", "false" ) == QLatin1String("true") ); + } + } + + node = node.nextSibling(); + } } QString Config::kdeHome() const { return mKdeHome; } QString Config::xdgDataHome() const { return mXdgDataHome; } QString Config::xdgConfigHome() const { return mXdgConfigHome; } void Config::setKdeHome( const QString &home ) { const QDir kdeHomeDir( home ); mKdeHome = kdeHomeDir.absolutePath(); } void Config::setXdgDataHome( const QString &dataHome ) { const QDir dataHomeDir( dataHome ); mXdgDataHome = dataHomeDir.absolutePath(); } void Config::setXdgConfigHome( const QString &configHome ) { const QDir configHomeDir( configHome ); mXdgConfigHome = configHomeDir.absolutePath(); } -void Config::insertItemConfig( const QString &itemName, const QString &collectionName ) -{ - mItemConfig.append( qMakePair( itemName, collectionName ) ); -} - -QList< QPair > Config::itemConfig() const -{ - return mItemConfig; -} - void Config::insertAgent( const QString &agent, bool sync ) { mAgents.append( qMakePair( agent, sync ) ); } QList > Config::agents() const { return mAgents; } diff --git a/akonadi/tests/testrunner/config.h b/akonadi/tests/testrunner/config.h index adc96d3cd..f4af67eb0 100644 --- a/akonadi/tests/testrunner/config.h +++ b/akonadi/tests/testrunner/config.h @@ -1,53 +1,53 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef CONFIG_H #define CONFIG_H #include #include #include class Config { public: + Config(); + ~Config(); static Config *instance( const QString &pathToConfig = QString() ); static void destroyInstance(); QString kdeHome() const; QString xdgDataHome() const; QString xdgConfigHome() const; - QList > itemConfig() const; QList > agents() const; protected: - Config(); void setKdeHome( const QString &home ); void setXdgDataHome( const QString &dataHome ); void setXdgConfigHome( const QString &configHome ); - void insertItemConfig( const QString &itemName, const QString &collectionName ); void insertAgent( const QString &agent, bool sync ); + private: + void readConfiguration(const QString &configFile); + private: QString mKdeHome; QString mXdgDataHome; QString mXdgConfigHome; - QList > mItemConfig; QList > mAgents; - static Config *mInstance; }; #endif diff --git a/akonadi/tests/testrunner/configreader.cpp b/akonadi/tests/testrunner/configreader.cpp deleted file mode 100644 index 5c65f6448..000000000 --- a/akonadi/tests/testrunner/configreader.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2008 Igor Trindade Oliveira - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - */ - -#include "configreader.h" -#include "config.h" -#include "symbols.h" - -#include -#include -#include -#include -#include - -ConfigReader::ConfigReader( const QString &configfile ) -{ - QDomDocument doc; - QFile file( configfile ); - - if ( !file.open( QIODevice::ReadOnly ) ) - qFatal( "error reading file: %s", qPrintable( configfile ) ); - - QString errorMsg; - if ( !doc.setContent( &file, &errorMsg ) ) - qFatal( "unable to parse config file: %s", qPrintable( errorMsg ) ); - - const QDomElement root = doc.documentElement(); - if ( root.tagName() != "config" ) - qFatal( "could not file root tag" ); - - const QString basePath = QFileInfo( configfile ).absolutePath() + "/"; - - QDomNode node = root.firstChild(); - while ( !node.isNull() ) { - const QDomElement element = node.toElement(); - if ( !element.isNull() ) { - if ( element.tagName() == "kdehome" ) { - setKdeHome( basePath + element.text() ); - } else if ( element.tagName() == "confighome" ) { - setXdgConfigHome( basePath + element.text() ); - } else if ( element.tagName() == "datahome" ) { - setXdgDataHome( basePath + element.text() ); - } else if ( element.tagName() == "item" ) { - insertItemConfig( element.attribute( "location" ), element.attribute( "collection" ) ); - } else if ( element.tagName() == "agent" ) { - insertAgent( element.text(), element.attribute( "synchronize", "false" ) == QLatin1String("true") ); - } - } - - node = node.nextSibling(); - } -} diff --git a/akonadi/tests/testrunner/configreader.h b/akonadi/tests/testrunner/configreader.h deleted file mode 100644 index db1e88d51..000000000 --- a/akonadi/tests/testrunner/configreader.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2008 Igor Trindade Oliveira - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - */ - -#ifndef CONFIGREADER_H -#define CONFIGREADER_H - -#include "config.h" - -class QString; - -class ConfigReader : public Config -{ - public: - ConfigReader( const QString &configfile ); -}; - -#endif diff --git a/akonadi/tests/testrunner/main.cpp b/akonadi/tests/testrunner/main.cpp index 71ba226b5..fbdb4e907 100644 --- a/akonadi/tests/testrunner/main.cpp +++ b/akonadi/tests/testrunner/main.cpp @@ -1,117 +1,116 @@ /* * * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include "setup.h" #include "shellscript.h" #include "testrunner.h" #include #include #include #include #include static SetupTest *setup = 0; static TestRunner *runner = 0; void sigHandler( int signal ) { kDebug() << "Received signal" << signal; static int sigCounter = 0; if ( sigCounter == 0 ) { // try clean shutdown if ( runner ) runner->terminate(); if ( setup ) setup->shutdown(); } else if ( sigCounter == 1 ) { // force shutdown if ( setup ) setup->shutdownHarder(); } else { // give up and just exit exit( 255 ); } ++sigCounter; } int main( int argc, char **argv ) { KAboutData aboutdata( "akonadi-TES", 0, ki18n( "Akonadi Testing Environment Setup" ), "1.0", ki18n( "Setup Environmnet" ), KAboutData::License_GPL, ki18n( "(c) 2008 Igor Trindade Oliveira" ) ); KCmdLineArgs::init( argc, argv, &aboutdata ); KCmdLineOptions options; options.add( "c" ).add( "config ", ki18n( "Configuration file to open" ), "config.xml" ); options.add( "!+[test]", ki18n( "Test to run automatically, interactive if none specified" ) ); options.add("testenv ", ki18n("Path where testenvironment would be saved")); KCmdLineArgs::addCmdLineOptions( options ); KApplication app; const KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); if ( args->isSet( "config" ) ) Config::instance( args->getOption( "config" ) ); #ifdef Q_OS_UNIX signal( SIGINT, sigHandler ); signal( SIGQUIT, sigHandler ); #endif setup = new SetupTest(); if ( !setup->startAkonadiDaemon() ) { delete setup; return 1; } ShellScript *sh = new ShellScript(); if( args->isSet("testenv")) sh->makeShellScript( args->getOption("testenv")); else sh->makeShellScript( setup->basePath() + "testenvironment.sh" ); if ( args->count() > 0 ) { QStringList testArgs; for ( int i = 0; i < args->count(); ++i ) testArgs << args->arg( i ); runner = new TestRunner( testArgs ); QObject::connect( setup, SIGNAL( setupDone() ), runner, SLOT( run() ) ); QObject::connect( runner, SIGNAL( finished() ), setup, SLOT( shutdown() ) ); } int exitCode = app.exec(); if ( runner ) { exitCode += runner->exitCode(); delete runner; } - Config::destroyInstance(); delete setup; setup = 0; delete sh; return exitCode; } diff --git a/kabc/formats/binary.desktop b/kabc/formats/binary.desktop index 4c1142654..becb9fd99 100644 --- a/kabc/formats/binary.desktop +++ b/kabc/formats/binary.desktop @@ -1,46 +1,47 @@ [Misc] Name=Binary Name[be]=Двойкавы Name[ca]=Binari Name[cs]=Binární Name[da]=Binær Name[de]=Binär Name[el]=Δυαδικό Name[es]=Binario Name[et]=Binaar Name[fr]=Binaire Name[ga]=Dénártha Name[gl]=Binario Name[hne]=द्विचर Name[hu]=Bináris Name[it]=Binario Name[ja]=バイナリ Name[km]=គោលពីរ Name[lt]=Dvejetainis Name[lv]=Binārs Name[nb]=Binært Name[nds]=Bineer Name[nl]=Binair Name[nn]=Binær Name[oc]=Binari Name[pa]=ਬਾਈਨਰੀ Name[pl]=Binarny Name[pt]=Binário Name[pt_BR]=Binário Name[ro]=Binar Name[ru]=Двоичный Name[se]=Binára +Name[sk]=Binárny Name[sl]=Dvojiško Name[sr]=Бинарни Name[sr@latin]=Binarni Name[sv]=Binär Name[th]=ไบนารี Name[tr]=İkili Name[uk]=Двійковий Name[x-test]=xxBinaryxx Name[zh_CN]=二进制 Name[zh_TW]=二進位 [Plugin] Type=binary X-KDE-Library=kabcformat_binary diff --git a/kabc/kabc_manager.desktop b/kabc/kabc_manager.desktop index fbe0fee6f..1fa107f58 100644 --- a/kabc/kabc_manager.desktop +++ b/kabc/kabc_manager.desktop @@ -1,45 +1,46 @@ # KDE Config File [Desktop Entry] Name=Contacts Name[be]=Кантакты Name[ca]=Contactes Name[cs]=Kontakty Name[da]=Kontakter Name[de]=Kontakte Name[el]=Επαφές Name[es]=Contactos Name[et]=Kontaktid Name[ga]=Teagmhálacha Name[gl]=Contactos Name[hne]=सम्पर्क Name[hu]=Partnerek Name[it]=Contatti Name[ja]=連絡先 Name[km]=ទំនាក់ទំនង​ Name[lt]=Kontaktai Name[lv]=Kontakti Name[nb]=Kontakter Name[nds]=Kontakten Name[nl]=Contactpersonen Name[nn]=Kontaktar Name[oc]=Contactes Name[pa]=ਸੰਪਰਕ Name[pl]=Kontakty Name[pt]=Contactos Name[pt_BR]=Contatos Name[ro]=Contacte Name[ru]=Контакты Name[se]=Oktavuođat +Name[sk]=Kontakty Name[sl]=Stiki Name[sr]=Контакти Name[sr@latin]=Kontakti Name[sv]=Kontakter Name[th]=ที่อยู่ติดต่อ Name[tr]=Kişiler Name[uk]=Контакти Name[x-test]=xxContactsxx Name[zh_CN]=联系人 Name[zh_TW]=聯絡人 Type=Service X-KDE-ServiceTypes=KResources/Manager X-KDE-ResourceFamily=contact diff --git a/kabc/plugins/dir/dir.desktop b/kabc/plugins/dir/dir.desktop index d73f2a2d6..84d057f1e 100644 --- a/kabc/plugins/dir/dir.desktop +++ b/kabc/plugins/dir/dir.desktop @@ -1,43 +1,45 @@ [Desktop Entry] Name=Folder Name[da]=Mappe Name[de]=Ordner Name[el]=Φάκελος Name[es]=Carpeta Name[et]=Kataloog Name[ga]=Fillteán Name[gl]=Cartafol Name[km]=ថត +Name[lv]=Mape Name[nb]=Mappe Name[nds]=Orner Name[pt]=Pasta Name[pt_BR]=Pasta Name[sv]=Katalog Name[tr]=Dizin Name[uk]=Тека Name[x-test]=xxFolderxx Name[zh_CN]=文件夹 Name[zh_TW]=資料夾 Comment=Provides access to contacts, each stored in a single file, in a given folder. Supports standard VCard file and other formats depending on availability of plugins. Comment[da]=Giver adgang til kontakter, hver lagret i en enkelt fil, i en given mappe. Understøtter standard VCard-fil og andre formater afhængigt af tilgængeligheden af plugins. Comment[de]=Ermöglicht Zugriff auf Kontakte, die in einzelnen Dateien in einem vorgegebenen Ordner gespeichert sind. Unterstützt Standard-VCard-Dateien und andere Formate abhängig von den verfügbaren Modulen. Comment[el]=Προσφέρει πρόσβαση σε επαφές, αποθηκευμένες σε ξεχωριστά αρχεία, σε ένα 
δοσμένο φάκελο. Υποστηρίζει τυπικά αρχεία VCard και άλλες μορφές αρχείων ανάλογα με τη διαθεσιμότητα των πρόσθετων. Comment[es]=Provee acceso a los contactos, cada uno almacenado en un archivo diferente, dentro de un directorio determinado. Soporta archivos VCard estándar y otros formatos dependiendo de la disponibilidad de los complementos Comment[et]=Võimaldab kasutada eraldi failidesse salvestatud kontakte määratud kataloogis. Toetab standardseid VCard-faile ja teisi vorminguid sõltuvalt pluginate olemasolust. Comment[ga]=Soláthraíonn sé seo rochtain ar theagmhálacha, gach ceann stóráilte i gcomhad aonair, i bhfillteán sonraithe. Tacaítear le comhaid v-Chárta agus le formáidí eile, ag brath ar na breiseáin atá ar fáil. Comment[gl]=Dá acceso aos contactos, cada un gardado nun ficheiro nun cartafol dado. Admite ficheiros vCard estándar e outros formatos, en función da dispoñibilidade de extensións. Comment[km]=ផ្ដល់​ការ​ចូលដំណើរការ​ទៅ​កាន់​ទំនាក់ទំនង ដែល​ទំនាក់ទំនង​នីមួយៗ​ត្រូវ​បានផ្ទុកនៅ​ក្នុង​ឯកសារ​តែ​មួយ នៅ​ក្នុង​ថត​ដែល​បានផ្ដល់​ឲ្យ ។ គាំទ្រ​ឯកសារ VCard និង​ទ្រង់ទ្រាយ​ផ្សេងៗ​ ដោយ​អាស្រ័យ​លើ​កម្មវិធី​ជំនួយ​ដែល​មាន ។ +Comment[lv]=Nodrošina pieeju kontaktiem, kas katrs saglabāts individuālā failā norādītajā mapē. Atbalsta standarta VCard failus un citus formātus, atkarībā no pieejamajiem spraudņiem. Comment[nb]=Gir tilgang til kontakter, lagret hver for seg i en enkelt fil, i en gitt mappe. Støtter standard vCard og andre formater avhengig av tilgjengelige programtillegg. Comment[nds]=Stellt Togriep op Kontakten praat, elkeen binnen een Datei binnen en angeven Orner. Ünnerstütt VCard-Dateien un anner Formaten na de verföögboren Modulen. Comment[pt]=Oferece o acesso aos contactos, estando cada um guardado num único ficheiro de uma dada pasta. Suporta os ficheiros VCard normais, assim como outros formatos, dependendo da disponibilidade dos 'plugins'. Comment[pt_BR]=Fornece acesso aos contatos, cada um armazenado em um único arquivo, em uma pasta informada. Suporta o formato de arquivos VCard padrão e outros formatos, dependendo da disponibilidade de plug-ins. Comment[sv]=Ger tillgång till kontakter, var och en lagrad i en enstaka fil, i en given katalog. Stöder vCard-standardfiler och andra format, beroende på tillgängliga insticksprogram. Comment[uk]=Надає доступ до контактів, кожен з яких зберігається у окремому файлі у вказаній теці. Підтримує стандартні файли VCard та файли у інших форматах, залежно від наявності відповідних додатків. Comment[x-test]=xxProvides access to contacts, each stored in a single file, in a given folder. Supports standard VCard file and other formats depending on availability of plugins.xx Comment[zh_CN]=提供对被存储在指定目录下的单独一个文件中的联系人的访问支持。支持标准 VCard 文件和其它插件所允许的格式。 Comment[zh_TW]=提供存取特定目錄下,個別儲存在單一檔案中的聯絡人的功能。支援標準的 VCard 檔,以及外掛程式所提供的其它格式檔案。 X-KDE-Library=kabc_directory Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=contact X-KDE-ResourceType=dir diff --git a/kabc/plugins/file/file.desktop b/kabc/plugins/file/file.desktop index e05618b04..474013695 100644 --- a/kabc/plugins/file/file.desktop +++ b/kabc/plugins/file/file.desktop @@ -1,65 +1,67 @@ [Desktop Entry] Name=File Name[be]=Файл Name[ca]=Fitxer Name[cs]=Soubor Name[da]=Fil Name[de]=Datei Name[el]=Αρχείο Name[es]=Archivo Name[et]=Fail Name[fr]=Fichier Name[ga]=Comhad Name[gl]=Ficheiro Name[hne]=फाइल Name[hu]=Fájl Name[ja]=ファイル Name[km]=ឯកសារ Name[lt]=Failas Name[lv]=Fails Name[nb]=Fil Name[nds]=Datei Name[nl]=Bestand Name[nn]=Fil Name[oc]=Fichièr Name[pa]=ਫਾਇਲ Name[pl]=Plik Name[pt]=Ficheiro Name[pt_BR]=Arquivo Name[ro]=Fișier Name[ru]=Файл Name[se]=Fiila +Name[sk]=Súbor Name[sl]=Datoteka Name[sr]=Фајл Name[sr@latin]=Fajl Name[sv]=Fil Name[th]=แฟ้ม Name[tr]=Dosya Name[uk]=Файл Name[x-test]=xxFilexx Name[zh_CN]=文件 Name[zh_TW]=檔案 Comment=Provides access to contacts stored in a single local file. Supports standard VCard files and other formats depending on available plugins. Comment[da]=Giver adgang til kontakter, hver lagret i en enkelt fil. Understøtter standard VCard-fil og andre formater afhængigt af tilgængelige af plugins. Comment[de]=Ermöglicht Zugriff auf Kontakte, die in einer einzigen Dateien lokal gespeichert sind. Unterstützt Standard-VCard-Dateien und andere Formate abhängig von den verfügbaren Modulen. Comment[el]=Προσφέρει πρόσβαση σε επαφές, αποθηκευμένες σε ένα τοπικό αρχείο. Υποστηρίζει τυπικά αρχεία VCard και άλλες μορφές αρχείων ανάλογα με τη διαθεσιμότητα των πρόσθετων. Comment[es]=Provee acceso a los contactos almacenados en un único archivo local. Soporta archivos VCard estándar y otros formatos dependiendo de la disponibilidad de los componentes. Comment[et]=Võimaldab kasutada kohalikku faili salvestatud kontakte. Toetab standardseid VCard-faile ja teisi vorminguid sõltuvalt pluginate olemasolust. Comment[ga]=Soláthraíonn sé seo rochtain ar theagmhálacha, stóráilte i gcomhad aonair. Tacaítear le comhaid v-Chárta agus formáidí eile, ag brath ar na breiseáin atá ar fáil. Comment[gl]=Dá acceso a contactos gardados nun único ficheiro local. Admite ficheiros vCard estándar e outros formatos en función das extensións dispoñíbeis. Comment[ja]=単一のローカルファイルに保存されている連絡先へのアクセスを提供します。標準の VCard ファイルと、利用可能なプラグインに応じたフォーマットをサポートします。 Comment[km]=ផ្ដល់​កា​រចូលដំណើរការ​ទៅ​ទំនាក់ទំនង​​នៅ​ក្នុង​ឯកសារ​មូលដ្ឋាន​តែ​មួយ ។ គាំទ្រ​ឯកសារ VCard ស្តង់ដារ និង​ទ្រង់ទ្រាយ​ផ្សេងៗ​ ដោយ​អាស្រ័យ​លើ​កម្មវិធី​ជំនួយ​ដែល​មាន ។ +Comment[lv]=Nodrošina piekļuvi kontaktiem, kas glabājas vienā lokālā failā. Atbalsta standarta VCard failus un citus formātus, atkarībā no pieejamajiem spraudņiem. Comment[nb]=Gir tilgang til kontakter, lagret i en enkelt lokal fil. Støtter standard vCard og andre formater avhengig av tilgjengelige programtillegg. Comment[nds]=Stellt Togriep op Kontakten praat, de in een enkel lokaal Datei wohrt warrt. Ünnerstütt VCard-Dateien un anner Formaten na de 'verföögboren Modulen. Comment[pt]=Oferece o acesso aos contactos, estando todos guardados num único ficheiro local. Suporta os ficheiros VCard normais, assim como outros formatos, dependendo da disponibilidade dos 'plugins'. Comment[pt_BR]=Fornece acesso aos contatos armazenados em um único arquivo local. Suporta o formato de arquivos VCard padrão e outros formatos, dependendo da disponibilidade de plug-ins. Comment[sv]=Ger tillgång till kontakter lagrade i en enda lokal fil. Stöder vCard-standardfiler och andra format, beroende på tillgängliga insticksprogram. Comment[uk]=Надає доступ до контактів, які зберігаються у окремому локальному файлі. Підтримує стандартні файли VCard та файли у інших форматах, залежно від наявності відповідних додатків. Comment[x-test]=xxProvides access to contacts stored in a single local file. Supports standard VCard files and other formats depending on available plugins.xx Comment[zh_CN]=提供对被存储在单独的本地文件中的联系人的访问支持。支持标准 VCard 文件和其它插件所允许的格式。 Comment[zh_TW]=提供存取儲存在單一檔案中的聯絡人的功能。支援標準的 VCard 檔,以及外掛程式所提供的其它格式檔案。 X-KDE-Library=kabc_file Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=contact X-KDE-ResourceType=file diff --git a/kabc/plugins/ldapkio/ldapkio.desktop b/kabc/plugins/ldapkio/ldapkio.desktop index e4c056ec0..f9070620e 100644 --- a/kabc/plugins/ldapkio/ldapkio.desktop +++ b/kabc/plugins/ldapkio/ldapkio.desktop @@ -1,44 +1,45 @@ [Desktop Entry] Name=LDAP Name[hne]=एलडीएपी Name[sr]=ЛДАП Name[th]=ระบบ LDAP Name[x-test]=xxLDAPxx Comment=Provides access to contacts stored on a LDAP directory server Comment[ca]=Proporciona l'accés als contactes emmagatzemats en un servidor de directori LDAP Comment[cs]=Poskytuje přístup ke kontaktům uloženým v adresářovém serveru LDAP Comment[da]=Giver adgang til kontakter lagret på en LDAP directory-server Comment[de]=Ermöglicht Zugriff auf Kontakte, die auf einem LDAP-Server gespeichert sind. Comment[el]=Προσφέρει πρόσβαση σε επαφές, αποθηκευμένες σε έναν εξυπηρετητή καταλόγου LDAP Comment[es]=Provee acceso a los contactos almacenados en un servidor de directorios LDAP Comment[et]=Võimaldab kasutada LDAP kataloogiserverisse salvestatud kontakte Comment[fr]=Fourni l'accès aux contacts stockés dans un annuaire LDAP Comment[ga]=Soláthraíonn sé seo rochtain ar theagmhálacha atá stóráilte in eolaire LDAP Comment[gl]=Dá acceso a contactos gardados nun servidor de directorio LDAP Comment[hu]=LDAP címtárkiszolgálókon tárolt névjegyek elérését biztosítja. Comment[it]=Fornisce accesso a contatti memorizzati su un server LDAP Comment[ja]=LDAP ディレクトリサーバに保存されている連絡先へのアクセスを提供します。 Comment[km]=ផ្ដល់​ការ​ចូលដំណើរការ​ទៅ​ទំនាក់ទំនង​ដែល​បានផ្ទុក​នៅ​លើ​ម៉ាស៊ីន​បម្រើ​ថត LDAP Comment[lv]=Nodrošina piekļuvi kontaktiem, kas glabājas LDAP direktorija serverī Comment[nb]=Gir tilgang til kontakter lagret på en LDAP-katalogtjener Comment[nds]=Stellt Togriep op Kontakten praat, de op en LDAP-Vertelenserver wohrt warrt Comment[nl]=Geeft toegang tot contactpersonen, opgeslagen op een LDAP-directoryserver Comment[nn]=Gjev tilgang til kontaktar lagra på ein LDAP-katalogtenar Comment[pl]=Daje dostęp do kontaktów przechowywanych na serwerze katalogowym LDAP Comment[pt]=Oferece o acesso aos contactos guardados num servidor de directório LDAP Comment[pt_BR]=Fornece acesso aos contatos armazenados em um servidor de pastas LDAP Comment[ru]=Хранение контактов на сервере каталогов LDAP +Comment[sk]=Poskytuje prístup ku kontaktom uloženým na LDAP adresárovom serveri. Comment[sl]=Nudi dostop do stikov, ki so shranjeni na imeniškem strežniku LDAP Comment[sr]=Пружа приступ контактима складиштеним на серверу ЛДАП именика Comment[sr@latin]=Pruža pristup kontaktima skladištenim na serveru LDAP imenika Comment[sv]=Ger tillgång till kontakter lagrade på en LDAP-katalogserver Comment[tr]=Bir LDAP dizin sunucusunda depolanmış kişilere erişim sağlar Comment[uk]=Надає доступ до контактів, які зберігаються на сервері каталогів LDAP Comment[x-test]=xxProvides access to contacts stored on a LDAP directory serverxx Comment[zh_CN]=提供对被存储在 LDAP 目录服务器上的联系人的访问支持 Comment[zh_TW]=提供存取儲存在 LDAP 目錄伺服器上的聯絡人的功能 X-KDE-Library=kabc_ldapkio Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=contact X-KDE-ResourceType=ldapkio diff --git a/kabc/plugins/net/net.desktop b/kabc/plugins/net/net.desktop index ed6d364a2..d23e1feb1 100644 --- a/kabc/plugins/net/net.desktop +++ b/kabc/plugins/net/net.desktop @@ -1,67 +1,69 @@ [Desktop Entry] Name=Network Name[be]=Сетка Name[ca]=Xarxa Name[cs]=Síť Name[da]=Netværk Name[de]=Netzwerk Name[el]=Δίκτυο Name[es]=Red Name[et]=Võrk Name[fr]=Réseau Name[ga]=Líonra Name[gl]=Rede Name[hne]=नेटवर्क Name[hu]=Hálózat Name[it]=Rete Name[ja]=ネットワーク Name[km]=បណ្តាញ Name[lt]=Tinklas Name[lv]=Tīkls Name[nb]=Nettverk Name[nds]=Nettwark Name[nl]=Netwerk Name[nn]=Nettverk Name[oc]=Ret Name[pa]=ਨੈੱਟਵਰਕ Name[pl]=Sieć Name[pt]=Rede Name[pt_BR]=Rede Name[ro]=Rețea Name[ru]=Сетевой файл Name[se]=Fierbmi +Name[sk]=Sieť Name[sl]=Omrežje Name[sr]=Мрежа Name[sr@latin]=Mreža Name[sv]=Nätverk Name[th]=ระบบเครือข่าย Name[tr]=Ağ Name[uk]=Мережа Name[x-test]=xxNetworkxx Name[zh_CN]=网络 Name[zh_TW]=網路 Comment=Provides access to contacts in remote files using KDE's network framework KIO. Supports standard VCard files and other formats depending on available plugins. Comment[da]=Giver adgang til kontakter i eksterne filer med brug af KDE's netværks-framework KIO. Understøtter standard VCard-filer og andre formater afhængigt af tilgængelige plugins. Comment[de]=Ermöglicht Zugriff auf Kontakte in entfernten Dateien durch das KIO-Netzwerksystem von KDE. Unterstützt Standard-VCard-Dateien und andere Formate abhängig von den verfügbaren Modulen. Comment[el]=Προσφέρει πρόσβαση σε επαφές σε απομακρυσμένα αρχεία με τη χρήση του συστήματος KIO του KDE. Υποστηρίζει τυπικά αρχεία VCard και άλλες μορφές αρχείων ανάλογα με τη διαθεσιμότητα των πρόσθετων. Comment[es]=Provee acceso a los contactos en un archivo remoto utilizando la infraestructura de red KIO de KDE. Soporta archivos VCard estándar y otros formatos dependiendo en la disponibilidad de los complementos. Comment[et]=Võimaldab kasutada võrgufaile KDE võrguraamistiku KIO abil. Toetab standardseid VCard-faile ja teisi vorminguid sõltuvalt pluginate olemasolust. Comment[ga]=Soláthraíonn sé seo rochtain ar theagmhálacha i gcianchomhaid tríd an gcreatlach líonra KIO atá cuid de KDE. Tacaítear le comhaid v-Chárta agus formáidí eile, ag brath ar na breiseáin atá ar fáil. Comment[gl]=Dá acceso a contactos gardados en ficheiros remotos mediante a infraestrutura de rede KIO, de KDE. Admite ficheiros vCard estándar e outros formatos en función das extensións dispoñíbeis. Comment[ja]=KDE のネットワークフレームワーク KIO を使って、リモートファイルに保存されている連絡先へのアクセスを提供します。標準の VCard ファイルと、利用可能なプラグインに応じたフォーマットをサポートします。 Comment[km]=ផ្ដល់​កា​រចូលដំណើរការ​ទៅកាន់​ទំនាក់ទំនង​ក្នុង​​ឯកសារ​ពី​ចម្ងាយ ដោយ​ប្រើ KIO ​គ្រោងការណ៍​បណ្ដាញ​របស់ KDE ។ គាំទ្រ​ឯកសារ VCard ស្តង់ដារ និង​ទ្រង់ទ្រាយ​ផ្សេងៗ​ទៀត ដោយ​អាស្រ័យ​លើ​កម្មវិធី​ជំនួយ​ដែល​លមាន ។ +Comment[lv]=Nodrošina piekļuvi kontaktiem attālinātos failus, izmantojot KDE tīklošanas ietvaru KIO. Atbalsta standarta VCard failus un citus formātus, atkarībā no pieejamajiem spraudņiem. Comment[nb]=Gir tilgang til kontakter i nettverksfiler, ved bruk av KDEs rammeverk KIO for nettverk. Støtter standard vCard og andre formater avhengig av tilgjengelige programtillegg. Comment[nds]=Stellt Togriep op Kontakten binnen feern Dateien praat, bruukt KDE sien Nettwark-Rahmenwark KIO. Ünnerstütt VCard-Dateien un anner Formaten na de verföögboren Modulen. Comment[pt]=Oferece o acesso aos contactos em ficheiros remotos, disponíveis através de um KIO da plataforma de rede do KDE. Suporta os ficheiros VCard normais, assim como outros formatos, dependendo da disponibilidade dos 'plugins'. Comment[pt_BR]=Fornece acesso aos contatos em arquivos remotos usando o KIO framework da rede do KDE. O suporte à arquivos VCard padrão e outros formatos dependem da disponibilidade de plug-ins. Comment[sv]=Ger tillgång till kontakter i fjärrfiler med användning av KDE:s I/O-ramverk. Stöder vCard-standardfiler och andra format, beroende på tillgängliga insticksprogram. Comment[tr]=KDE'nin ağ iskeleti KIO'yu kullanarak uzak dosyalardaki kişilere erişim sağlar. Mevcut eklentilere bağlı olarak standart VCard dosyalarını ve diğer dosya biçimleri destekler. Comment[uk]=Надає доступ до контактів, які зберігаються у віддалених файлах, за допомогою мережевих засобів KIO KDE. Підтримує стандартні файли VCard та файли у інших форматах, залежно від наявності відповідних додатків. Comment[x-test]=xxProvides access to contacts in remote files using KDE's network framework KIO. Supports standard VCard files and other formats depending on available plugins.xx Comment[zh_CN]=通过 KDE 的网络透明框架 KIO,提供对被存储在远程文件中的联系人的访问支持。支持标准 VCard 文件和其它插件所允许的格式。 Comment[zh_TW]=提供存取使用 KDE 網路架構 KIO 儲存在遠端檔案中的聯絡人。支援標準的 VCard 檔,以及外掛程式所提供的其它格式檔案。 X-KDE-Library=kabc_net Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=contact X-KDE-ResourceType=net diff --git a/kcal/incidenceformatter.cpp b/kcal/incidenceformatter.cpp index a4d11cde5..ef75db60c 100644 --- a/kcal/incidenceformatter.cpp +++ b/kcal/incidenceformatter.cpp @@ -1,2361 +1,2428 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (c) 2004 Reinhold Kainhofer Copyright (c) 2005 Rafal Rzepecki This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and provides static functions for formatting Incidences for various purposes. @brief Provides methods to format Incidences in various ways for display purposes. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #include "incidenceformatter.h" #include "attachment.h" #include "event.h" #include "todo.h" #include "journal.h" #include "calendar.h" #include "calendarlocal.h" #include "icalformat.h" #include "freebusy.h" #include "calendarresources.h" #include "kpimutils/email.h" #include "kabc/phonenumber.h" #include "kabc/vcardconverter.h" #include "kabc/stdaddressbook.h" #include #include #include #include #include #include #include #include #include #include using namespace KCal; /******************************************************************* * Helper functions for the extensive display (event viewer) *******************************************************************/ //@cond PRIVATE static QString eventViewerAddLink( const QString &ref, const QString &text, bool newline = true ) { QString tmpStr( "" + text + "" ); if ( newline ) { tmpStr += '\n'; } return tmpStr; } static QString eventViewerAddTag( const QString &tag, const QString &text ) { int numLineBreaks = text.count( "\n" ); QString str = '<' + tag + '>'; QString tmpText = text; QString tmpStr = str; if( numLineBreaks >= 0 ) { if ( numLineBreaks > 0 ) { int pos = 0; QString tmp; for ( int i = 0; i <= numLineBreaks; ++i ) { pos = tmpText.indexOf( "\n" ); tmp = tmpText.left( pos ); tmpText = tmpText.right( tmpText.length() - pos - 1 ); tmpStr += tmp + "
"; } } else { tmpStr += tmpText; } } tmpStr += "'; return tmpStr; } static QString eventViewerFormatCategories( Incidence *event ) { QString tmpStr; if ( !event->categoriesStr().isEmpty() ) { if ( event->categories().count() == 1 ) { tmpStr = eventViewerAddTag( "h3", i18n( "Category" ) ); } else { tmpStr = eventViewerAddTag( "h3", i18n( "Categories" ) ); } tmpStr += eventViewerAddTag( "p", event->categoriesStr() ); } return tmpStr; } static QString linkPerson( const QString &email, QString name, QString uid, const QString &iconPath ) { // Make the search, if there is an email address to search on, // and either name or uid is missing if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) { KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); KABC::Addressee::List addressList = add_book->findByEmail( email ); KABC::Addressee o = ( !addressList.isEmpty() ? addressList.first() : KABC::Addressee() ); if ( !o.isEmpty() && addressList.size() < 2 ) { if ( name.isEmpty() ) { // No name set, so use the one from the addressbook name = o.formattedName(); } uid = o.uid(); } else { // Email not found in the addressbook. Don't make a link uid.clear(); } } // Show the attendee QString tmpString = "
  • "; if ( !uid.isEmpty() ) { // There is a UID, so make a link to the addressbook if ( name.isEmpty() ) { // Use the email address for text tmpString += eventViewerAddLink( "uid:" + uid, email ); } else { tmpString += eventViewerAddLink( "uid:" + uid, name ); } } else { // No UID, just show some text tmpString += ( name.isEmpty() ? email : name ); } tmpString += '\n'; // Make the mailto link if ( !email.isEmpty() && !iconPath.isNull() ) { KUrl mailto; mailto.setProtocol( "mailto" ); mailto.setPath( email ); tmpString += eventViewerAddLink( mailto.url(), "" ); } tmpString += "
  • \n"; return tmpString; } static QString eventViewerFormatAttendees( Incidence *event ) { QString tmpStr; Attendee::List attendees = event->attendees(); if ( attendees.count() ) { KIconLoader *iconLoader = KIconLoader::global(); const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small ); // Add organizer link tmpStr += eventViewerAddTag( "h4", i18n( "Organizer" ) ); tmpStr += "
      "; tmpStr += linkPerson( event->organizer().email(), event->organizer().name(), QString(), iconPath ); tmpStr += "
    "; // Add attendees links tmpStr += eventViewerAddTag( "h4", i18n( "Attendees" ) ); tmpStr += "
      "; Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; tmpStr += linkPerson( a->email(), a->name(), a->uid(), iconPath ); if ( !a->delegator().isEmpty() ) { tmpStr += i18n( " (delegated by %1)", a->delegator() ); } if ( !a->delegate().isEmpty() ) { tmpStr += i18n( " (delegated to %1)", a->delegate() ); } } tmpStr += "
    "; } return tmpStr; } static QString eventViewerFormatAttachments( Incidence *i ) { QString tmpStr; Attachment::List as = i->attachments(); if ( as.count() > 0 ) { Attachment::List::ConstIterator it; for ( it = as.constBegin(); it != as.constEnd(); ++it ) { if ( (*it)->isUri() ) { tmpStr += eventViewerAddLink( (*it)->uri(), (*it)->label() ); tmpStr += "
    "; } } } return tmpStr; } /* FIXME:This function depends of kaddressbook. Is necessary a new type of event? */ static QString eventViewerFormatBirthday( Event *event ) { if ( !event ) { return QString(); } if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" ) { return QString(); } QString uid_1 = event->customProperty( "KABC", "UID-1" ); QString name_1 = event->customProperty( "KABC", "NAME-1" ); QString email_1= event->customProperty( "KABC", "EMAIL-1" ); KIconLoader *iconLoader = KIconLoader::global(); const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small ); //TODO: add a tart icon QString tmpString = "
      "; tmpString += linkPerson( email_1, name_1, uid_1, iconPath ); if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) { QString uid_2 = event->customProperty( "KABC", "UID-2" ); QString name_2 = event->customProperty( "KABC", "NAME-2" ); QString email_2= event->customProperty( "KABC", "EMAIL-2" ); tmpString += linkPerson( email_2, name_2, uid_2, iconPath ); } tmpString += "
    "; return tmpString; } static QString eventViewerFormatHeader( Incidence *incidence ) { QString tmpStr = ""; // show icons KIconLoader *iconLoader = KIconLoader::global(); tmpStr += ""; tmpStr += ""; tmpStr += "
    "; // TODO: KDE5. Make the function QString Incidence::getPixmap() so we don't // need downcasting. if ( incidence->type() == "Todo" ) { tmpStr += "( incidence ); if ( !todo->isCompleted() ) { tmpStr += iconLoader->iconPath( "view-calendar-tasks", KIconLoader::Small ); } else { tmpStr += iconLoader->iconPath( "task-complete", KIconLoader::Small ); } tmpStr += "\">"; } if ( incidence->type() == "Event" ) { tmpStr += "iconPath( "view-calendar-day", KIconLoader::Small ) + "\">"; } if ( incidence->type() == "Journal" ) { tmpStr += "iconPath( "view-pim-journal", KIconLoader::Small ) + "\">"; } if ( incidence->isAlarmEnabled() ) { tmpStr += "iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) + "\">"; } if ( incidence->recurs() ) { tmpStr += "iconPath( "edit-redo", KIconLoader::Small ) + "\">"; } if ( incidence->isReadOnly() ) { tmpStr += "iconPath( "object-locked", KIconLoader::Small ) + "\">"; } tmpStr += "" + eventViewerAddTag( "h2", incidence->richSummary() ) + "
    "; return tmpStr; } static QString eventViewerFormatEvent( Event *event, KDateTime::Spec spec ) { if ( !event ) { return QString(); } QString tmpStr = eventViewerFormatHeader( event ); tmpStr += ""; if ( !event->location().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += ""; if ( event->allDay() ) { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; tmpStr += ""; } } else { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; if ( event->hasEndDate() && event->dtStart() != event->dtEnd() ) { tmpStr += ""; } else { tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += ""; } } tmpStr += ""; if ( event->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += "
    " + i18n( "Location" ) + "" + event->richLocation() + "
    " + i18n( "Time" ) + "" + i18nc( " - ","%1 - %2", IncidenceFormatter::dateToString( event->dtStart(), true, spec ), IncidenceFormatter::dateToString( event->dtEnd(), true, spec ) ) + "" + i18n( "Date" ) + "" + i18nc( "date as string","%1", IncidenceFormatter::dateToString( event->dtStart(), true, spec ) ) + "" + i18n( "Time" ) + "" + i18nc( " - ","%1 - %2", IncidenceFormatter::dateToString( event->dtStart(), true, spec ), IncidenceFormatter::dateToString( event->dtEnd(), true, spec ) ) + "" + i18n( "Time" ) + "" + i18nc( " - ","%1 - %2", IncidenceFormatter::timeToString( event->dtStart(), true, spec ), IncidenceFormatter::timeToString( event->dtEnd(), true, spec ) ) + "" + IncidenceFormatter::timeToString( event->dtStart(), true, spec ) + "
    " + i18n( "Date" ) + "" + i18nc( "date as string","%1", IncidenceFormatter::dateToString( event->dtStart(), true, spec ) ) + "
    " + i18n( "Birthday" ) + "" + eventViewerFormatBirthday( event ) + "
    "; return tmpStr; } if ( !event->description().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += "" + eventViewerAddTag( "p", event->richDescription() ) + ""; tmpStr += ""; } if ( event->categories().count() > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += i18np( "1 category", "%1 categories", event->categories().count() ) + ""; tmpStr += "" + event->categoriesStr() + ""; tmpStr += ""; } if ( event->recurs() ) { KDateTime dt = event->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() ); tmpStr += ""; tmpStr += "" + i18n( "Next Occurrence" )+ ""; tmpStr += "" + ( dt.isValid() ? KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) : i18nc( "no date", "none" ) ) + ""; tmpStr += ""; } tmpStr += ""; tmpStr += eventViewerFormatAttendees( event ); tmpStr += ""; int attachmentCount = event->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += i18np( "1 attachment", "%1 attachments", attachmentCount )+ ""; tmpStr += "" + eventViewerFormatAttachments( event ) + ""; tmpStr += ""; } KDateTime kdt = event->created().toTimeSpec( spec ); tmpStr += ""; tmpStr += "

    " + i18n( "Creation date: %1", KGlobal::locale()->formatDateTime( kdt.dateTime(), KLocale::ShortDate ) ) + ""; return tmpStr; } static QString eventViewerFormatTodo( Todo *todo, KDateTime::Spec spec ) { if ( !todo ) { return QString(); } QString tmpStr = eventViewerFormatHeader( todo ); if ( !todo->location().isEmpty() ) { tmpStr += eventViewerAddTag( "b", i18n(" Location: %1", todo->richLocation() ) ); tmpStr += "
    "; } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { tmpStr += i18n( "Due on: %1", IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay(), true, spec ) ); } if ( !todo->description().isEmpty() ) { tmpStr += eventViewerAddTag( "p", todo->richDescription() ); } tmpStr += eventViewerFormatCategories( todo ); if ( todo->priority() > 0 ) { tmpStr += i18n( "

    Priority: %1

    ", todo->priority() ); } else { tmpStr += i18n( "

    Priority: %1

    ", i18n( "Unspecified" ) ); } tmpStr += i18n( "

    %1 % completed

    ", todo->percentComplete() ); if ( todo->recurs() ) { KDateTime dt = todo->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() ); tmpStr += eventViewerAddTag( "p", "" + i18n( "This is a recurring to-do. The next occurrence will be on %1.", KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) ) + "" ); } tmpStr += eventViewerFormatAttendees( todo ); tmpStr += eventViewerFormatAttachments( todo ); KDateTime kdt = todo->created().toTimeSpec( spec ); tmpStr += "

    " + i18n( "Creation date: %1", KGlobal::locale()->formatDateTime( kdt.dateTime(), KLocale::ShortDate ) ) + ""; return tmpStr; } static QString eventViewerFormatJournal( Journal *journal, KDateTime::Spec spec ) { if ( !journal ) { return QString(); } QString tmpStr = eventViewerFormatHeader( journal ); tmpStr += eventViewerAddTag( "h3", i18n( "Journal for %1", IncidenceFormatter::dateToString( journal->dtStart(), false, spec ) ) ); if ( !journal->description().isEmpty() ) { tmpStr += eventViewerAddTag( "p", journal->richDescription() ); } return tmpStr; } static QString eventViewerFormatFreeBusy( FreeBusy *fb, KDateTime::Spec spec ) { Q_UNUSED( spec ); if ( !fb ) { return QString(); } QString tmpStr( eventViewerAddTag( "h2", i18n( "Free/Busy information for %1", fb->organizer().fullName() ) ) ); tmpStr += eventViewerAddTag( "h4", i18n( "Busy times in date range %1 - %2:", KGlobal::locale()->formatDate( fb->dtStart().date(), KLocale::ShortDate ), KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) ) ); QList periods = fb->busyPeriods(); QString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) ); QList::iterator it; for ( it = periods.begin(); it != periods.end(); ++it ) { Period per = *it; if ( per.hasDuration() ) { int dur = per.duration().asSeconds(); QString cont; if ( dur >= 3600 ) { cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 ); dur %= 3600; } if ( dur >= 60 ) { cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 ); dur %= 60; } if ( dur > 0 ) { cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur ); } text += i18nc( "startDate for duration", "%1 for %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), cont ); text += "
    "; } else { if ( per.start().date() == per.end().date() ) { text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3", KGlobal::locale()->formatDate( per.start().date() ), KGlobal::locale()->formatTime( per.start().time() ), KGlobal::locale()->formatTime( per.end().time() ) ); } else { text += i18nc( "fromDateTime - toDateTime", "%1 - %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), KGlobal::locale()->formatDateTime( per.end().dateTime(), KLocale::LongDate ) ); } text += "
    "; } } tmpStr += eventViewerAddTag( "p", text ); return tmpStr; } //@endcond //@cond PRIVATE class KCal::IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor { public: EventViewerVisitor() : mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() ) { mSpec = spec; mResult = ""; return incidence->accept( *this ); } QString result() const { return mResult; } protected: bool visit( Event *event ) { mResult = eventViewerFormatEvent( event, mSpec ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = eventViewerFormatTodo( todo, mSpec ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = eventViewerFormatJournal( journal, mSpec ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = eventViewerFormatFreeBusy( fb, mSpec ); return !mResult.isEmpty(); } protected: KDateTime::Spec mSpec; QString mResult; }; //@endcond QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence ) { return extensiveDisplayStr( incidence, KDateTime::Spec() ); } QString IncidenceFormatter::extensiveDisplayStr( IncidenceBase *incidence, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } EventViewerVisitor v; if ( v.act( incidence, spec ) ) { return v.result(); } else { return QString(); } } /******************************************************************* * Helper functions for the body part formatter of kmail *******************************************************************/ +//TODO: 4.4: remove "meeting" from the invitation strings + //@cond PRIVATE static QString string2HTML( const QString &str ) { - return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal ); + return Qt::escape( str ); } static QString cleanHtml( const QString &html ) { QRegExp rx( "]*>(.*)", Qt::CaseInsensitive ); rx.indexIn( html ); QString body = rx.cap( 1 ); return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() ); } static QString eventStartTimeStr( Event *event ) { QString tmp; if ( !event->allDay() ) { tmp = i18nc( "%1: Start Date, %2: Start Time", "%1 %2", - IncidenceFormatter::dateToString( event->dtStart() ), - IncidenceFormatter::timeToString( event->dtStart() ) ); + IncidenceFormatter::dateToString( event->dtStart(), true, + KSystemTimeZones::local() ), + IncidenceFormatter::timeToString( event->dtStart(), true, + KSystemTimeZones::local() ) ); } else { tmp = i18nc( "%1: Start Date", "%1 (all day)", - IncidenceFormatter::dateToString( event->dtStart() ) ); + IncidenceFormatter::dateToString( event->dtStart(), true, + KSystemTimeZones::local() ) ); } return tmp; } static QString eventEndTimeStr( Event *event ) { QString tmp; if ( event->hasEndDate() && event->dtEnd().isValid() ) { if ( !event->allDay() ) { tmp = i18nc( "%1: End Date, %2: End Time", "%1 %2", - IncidenceFormatter::dateToString( event->dtEnd() ), - IncidenceFormatter::timeToString( event->dtEnd() ) ); + IncidenceFormatter::dateToString( event->dtEnd(), true, + KSystemTimeZones::local() ), + IncidenceFormatter::timeToString( event->dtEnd(), true, + KSystemTimeZones::local() ) ); } else { tmp = i18nc( "%1: End Date", "%1 (all day)", - IncidenceFormatter::dateToString( event->dtEnd() ) ); + IncidenceFormatter::dateToString( event->dtEnd(), true, + KSystemTimeZones::local() ) ); } } else { tmp = i18n( "Unspecified" ); } return tmp; } static QString invitationRow( const QString &cell1, const QString &cell2 ) { return "" + cell1 + "" + cell2 + "\n"; } static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode ) { + // if description and comment -> use both + // if description, but no comment -> use the desc as the comment (and no desc) + // if comment, but no description -> use the comment and no description + QString html; QString descr; QStringList comments; - if ( !incidence->description().isEmpty() ) { - if ( !incidence->descriptionIsRich() ) { - descr = string2HTML( incidence->description() ); - } else { - descr = incidence->richDescription(); - if ( noHtmlMode ) { - descr = cleanHtml( descr ); + + if ( incidence->comments().isEmpty() ) { + if ( !incidence->description().isEmpty() ) { + // use description as comments + if ( !incidence->descriptionIsRich() ) { + comments << string2HTML( incidence->description() ); + } else { + comments << incidence->richDescription(); + if ( noHtmlMode ) { + comments[0] = cleanHtml( comments[0] ); + } } - descr = eventViewerAddTag( "p", descr ); } - } - - if ( incidence->comments().isEmpty() && !descr.isEmpty() ) { - comments << descr; + //else desc and comments are empty } else { - comments = incidence->comments(); + // non-empty comments + for ( int i=0; i < incidence->comments().count(); ++i ) { + comments[i] = string2HTML( incidence->comments()[i] ); + } + if ( !incidence->description().isEmpty() ) { + // use description too + if ( !incidence->descriptionIsRich() ) { + descr = string2HTML( incidence->description() ); + } else { + descr = incidence->richDescription(); + if ( noHtmlMode ) { + descr = cleanHtml( descr ); + } + } + } } if( !descr.isEmpty() ) { - html += "
    " + i18n( "Description:" ) + "
     "; - html += descr + "
    "; + html += "

    "; + html += ""; + html += ""; + html += ""; + html += "
    " + + eventViewerAddTag( "u", i18n( "Description:" ) ) + + "
    " + descr + "
    "; } + if ( !comments.isEmpty() ) { - html += "
    " + i18n( "Comments:" ) + ""; + html += "
     "; + html += "

    "; + html += ""; + html += ""; + html += ""; if ( comments.count() > 1 ) { - html += "
      "; - for ( int i = 0; i < comments.count(); ++i ) { - html += "
    • " + string2HTML( comments[i] ) + "
    • "; + html += "
    "; } else { - html += string2HTML( comments[0] ); + html += ""; } - html += "
    " + + eventViewerAddTag( "u", i18n( "Comments:" ) ) + + "
      "; + for ( int i=0; i < comments.count(); ++i ) { + html += "
    • " + comments[i] + "
    • "; } - html += "
    "; + html += "
    " + comments[0] + "
    "; + html += "

    "; } return html; } static QString invitationDetailsEvent( Event *event, bool noHtmlMode ) { // Meeting details are formatted into an HTML table if ( !event ) { return QString(); } - QString html; - QString tmp; - QString sSummary = i18n( "Summary unspecified" ); - if ( ! event->summary().isEmpty() ) { + if ( !event->summary().isEmpty() ) { if ( !event->summaryIsRich() ) { sSummary = string2HTML( event->summary() ); } else { sSummary = event->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } - sSummary = eventViewerAddTag( "p", sSummary ); } } QString sLocation = i18n( "Location unspecified" ); - if ( ! event->location().isEmpty() ) { + if ( !event->location().isEmpty() ) { if ( !event->locationIsRich() ) { sLocation = string2HTML( event->location() ); } else { sLocation = event->richLocation(); if ( noHtmlMode ) { sLocation = cleanHtml( sLocation ); } - sLocation = eventViewerAddTag( "p", sLocation ); } } QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" ); - html = QString( "

    \n" ).arg( dir ); - html += "\n"; + QString html = QString( "
    \n" ).arg( dir ); // Meeting summary & location rows html += invitationRow( i18n( "What:" ), sSummary ); html += invitationRow( i18n( "Where:" ), sLocation ); // Meeting Start Time Row html += invitationRow( i18n( "Start Time:" ), eventStartTimeStr( event ) ); // Meeting End Time Row html += invitationRow( i18n( "End Time:" ), eventEndTimeStr( event ) ); // Meeting Duration Row if ( !event->allDay() && event->hasEndDate() && event->dtEnd().isValid() ) { - tmp.clear(); + QString tmp; QTime sDuration( 0, 0, 0 ), t; int secs = event->dtStart().secsTo( event->dtEnd() ); t = sDuration.addSecs( secs ); if ( t.hour() > 0 ) { tmp += i18np( "1 hour ", "%1 hours ", t.hour() ); } if ( t.minute() > 0 ) { tmp += i18np( "1 minute ", "%1 minutes ", t.minute() ); } html += invitationRow( i18n( "Duration:" ), tmp ); } if ( event->recurs() ) { html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) ); } - html += "
    \n"; + html += "
    "; html += invitationsDetailsIncidence( event, noHtmlMode ); - html += "\n"; return html; } static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode ) { // To-do details are formatted into an HTML table if ( !todo ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); QString sDescr = i18n( "Description unspecified" ); if ( ! todo->summary().isEmpty() ) { sSummary = todo->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } if ( ! todo->description().isEmpty() ) { sDescr = todo->description(); if ( noHtmlMode ) { sDescr = cleanHtml( sDescr ); } } - QString html( "\n" ); + QString html = "
    "; html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
    \n"; html += invitationsDetailsIncidence( todo, noHtmlMode ); return html; } static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode ) { if ( !journal ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); QString sDescr = i18n( "Description unspecified" ); if ( ! journal->summary().isEmpty() ) { sSummary = journal->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } if ( ! journal->description().isEmpty() ) { sDescr = journal->richDescription(); if ( noHtmlMode ) { sDescr = cleanHtml( sDescr ); } } - QString html( "\n" ); + QString html = "
    "; html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Date:" ), IncidenceFormatter::dateToString( journal->dtStart(), false, journal->dtStart().timeSpec() ) ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
    \n"; html += invitationsDetailsIncidence( journal, noHtmlMode ); return html; } static QString invitationDetailsFreeBusy( FreeBusy *fb ) { if ( !fb ) { return QString(); } - QString html( "\n" ); + QString html = "
    "; html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() ); html += invitationRow( i18n( "Start date:" ), IncidenceFormatter::dateToString( fb->dtStart(), true, fb->dtStart().timeSpec() ) ); html += invitationRow( i18n( "End date:" ), KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) ); html += "\n"; html += "\n"; QList periods = fb->busyPeriods(); QList::iterator it; for ( it = periods.begin(); it != periods.end(); ++it ) { Period per = *it; if ( per.hasDuration() ) { int dur = per.duration().asSeconds(); QString cont; if ( dur >= 3600 ) { cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 ); dur %= 3600; } if ( dur >= 60 ) { cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 ); dur %= 60; } if ( dur > 0 ) { cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur ); } html += invitationRow( QString(), i18nc( "startDate for duration", "%1 for %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), cont ) ); } else { QString cont; if ( per.start().date() == per.end().date() ) { cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3", KGlobal::locale()->formatDate( per.start().date() ), KGlobal::locale()->formatTime( per.start().time() ), KGlobal::locale()->formatTime( per.end().time() ) ); } else { cont = i18nc( "fromDateTime - toDateTime", "%1 - %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), KGlobal::locale()->formatDateTime( per.end().dateTime(), KLocale::LongDate ) ); } html += invitationRow( QString(), cont ); } } html += "

    Busy periods given in this free/busy object:
    \n"; return html; } static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg ) { if ( !msg || !event ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This event has been published" ); case iTIPRequest: if ( event->revision() > 0 ) { + //TODO: 4.4, remove the h3 tag return i18n( "

    This meeting has been updated

    " ); } else { return i18n( "You have been invited to this meeting" ); } case iTIPRefresh: return i18n( "This invitation was refreshed" ); case iTIPCancel: return i18n( "This meeting has been canceled" ); case iTIPAdd: return i18n( "Addition to the meeting invitation" ); case iTIPReply: { Attendee::List attendees = event->attendees(); if( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if ( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1" << "but is" << attendees.count(); } Attendee *attendee = *attendees.begin(); QString attendeeName = attendee->name(); if ( attendeeName.isEmpty() ) { attendeeName = attendee->email(); } if ( attendeeName.isEmpty() ) { attendeeName = i18n( "Sender" ); } QString delegatorName, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName ); if ( delegatorName.isEmpty() ) { delegatorName = attendee->delegator(); } switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "%1 indicates this invitation still needs some action", attendeeName ); case Attendee::Accepted: if ( delegatorName.isEmpty() ) { return i18n( "%1 accepts this meeting invitation", attendeeName ); } return i18n( "%1 accepts this meeting invitation on behalf of %2", attendeeName, delegatorName ); case Attendee::Tentative: if ( delegatorName.isEmpty() ) { return i18n( "%1 tentatively accepts this meeting invitation", attendeeName ); } return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2", attendeeName, delegatorName ); case Attendee::Declined: if ( delegatorName.isEmpty() ) { return i18n( "%1 declines this meeting invitation", attendeeName ); } return i18n( "%1 declines this meeting invitation on behalf of %2", attendeeName, delegatorName ); case Attendee::Delegated: { QString delegate, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); if ( delegate.isEmpty() ) { delegate = attendee->delegate(); } if ( !delegate.isEmpty() ) { return i18n( "%1 has delegated this meeting invitation to %2", attendeeName, delegate ); } return i18n( "%1 has delegated this meeting invitation", attendeeName ); } case Attendee::Completed: return i18n( "This meeting invitation is now completed" ); case Attendee::InProcess: return i18n( "%1 is still processing the invitation", attendeeName ); default: return i18n( "Unknown response to this meeting invitation" ); } break; } case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() ); } return QString(); } static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg ) { if ( !msg || !todo ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This to-do has been published" ); case iTIPRequest: if ( todo->revision() > 0 ) { return i18n( "This to-do has been updated" ); } else { return i18n( "You have been assigned this to-do" ); } case iTIPRefresh: return i18n( "This to-do was refreshed" ); case iTIPCancel: return i18n( "This to-do was canceled" ); case iTIPAdd: return i18n( "Addition to the to-do" ); case iTIPReply: { Attendee::List attendees = todo->attendees(); if ( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if ( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1" << "but is" << attendees.count(); } Attendee *attendee = *attendees.begin(); switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "Sender indicates this to-do assignment still needs some action" ); case Attendee::Accepted: return i18n( "Sender accepts this to-do" ); case Attendee::Tentative: return i18n( "Sender tentatively accepts this to-do" ); case Attendee::Declined: return i18n( "Sender declines this to-do" ); case Attendee::Delegated: { QString delegate, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); if ( delegate.isEmpty() ) { delegate = attendee->delegate(); } if ( !delegate.isEmpty() ) { return i18n( "Sender has delegated this request for the to-do to %1", delegate ); } return i18n( "Sender has delegated this request for the to-do " ); } case Attendee::Completed: return i18n( "The request for this to-do is now completed" ); case Attendee::InProcess: return i18n( "Sender is still processing the invitation" ); default: return i18n( "Unknown response to this to-do" ); } break; } case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() ); } return QString(); } static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg ) { // TODO: Several of the methods are not allowed for journals, so remove them. if ( !msg || !journal ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This journal has been published" ); case iTIPRequest: return i18n( "You have been assigned this journal" ); case iTIPRefresh: return i18n( "This journal was refreshed" ); case iTIPCancel: return i18n( "This journal was canceled" ); case iTIPAdd: return i18n( "Addition to the journal" ); case iTIPReply: { Attendee::List attendees = journal->attendees(); if ( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1" << "but is" << attendees.count(); } Attendee *attendee = *attendees.begin(); switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "Sender indicates this journal assignment still needs some action" ); case Attendee::Accepted: return i18n( "Sender accepts this journal" ); case Attendee::Tentative: return i18n( "Sender tentatively accepts this journal" ); case Attendee::Declined: return i18n( "Sender declines this journal" ); case Attendee::Delegated: return i18n( "Sender has delegated this request for the journal" ); case Attendee::Completed: return i18n( "The request for this journal is now completed" ); case Attendee::InProcess: return i18n( "Sender is still processing the invitation" ); default: return i18n( "Unknown response to this journal" ); } break; } case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() ); } return QString(); } static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg ) { if ( !msg || !fb ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This free/busy list has been published" ); case iTIPRequest: return i18n( "The free/busy list has been requested" ); case iTIPRefresh: return i18n( "This free/busy list was refreshed" ); case iTIPCancel: return i18n( "This free/busy list was canceled" ); case iTIPAdd: return i18n( "Addition to the free/busy list" ); case iTIPNoMethod: default: return i18n( "Error: Free/Busy iMIP message with unknown method: '%1'", msg->method() ); } } //@endcond //@cond PRIVATE class KCal::IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor { public: ScheduleMessageVisitor() : mMessage(0) { mResult = ""; } bool act( IncidenceBase *incidence, ScheduleMessage *msg ) { mMessage = msg; return incidence->accept( *this ); } QString result() const { return mResult; } protected: QString mResult; ScheduleMessage *mMessage; }; class KCal::IncidenceFormatter::InvitationHeaderVisitor : public IncidenceFormatter::ScheduleMessageVisitor { protected: bool visit( Event *event ) { mResult = invitationHeaderEvent( event, mMessage ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = invitationHeaderTodo( todo, mMessage ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = invitationHeaderJournal( journal, mMessage ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = invitationHeaderFreeBusy( fb, mMessage ); return !mResult.isEmpty(); } }; class KCal::IncidenceFormatter::InvitationBodyVisitor : public IncidenceFormatter::ScheduleMessageVisitor { public: InvitationBodyVisitor( bool noHtmlMode ) : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) { } protected: bool visit( Event *event ) { mResult = invitationDetailsEvent( event, mNoHtmlMode ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = invitationDetailsTodo( todo, mNoHtmlMode ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = invitationDetailsJournal( journal, mNoHtmlMode ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = invitationDetailsFreeBusy( fb ); return !mResult.isEmpty(); } private: bool mNoHtmlMode; }; //@endcond QString InvitationFormatterHelper::generateLinkURL( const QString &id ) { return id; } //@cond PRIVATE class IncidenceFormatter::IncidenceCompareVisitor : public IncidenceBase::Visitor { public: IncidenceCompareVisitor() : mExistingIncidence( 0 ) {} bool act( IncidenceBase *incidence, Incidence *existingIncidence ) { if ( !existingIncidence ) { return false; } Incidence *inc = dynamic_cast( incidence ); if ( inc && inc->revision() <= existingIncidence->revision() ) { return false; } mExistingIncidence = existingIncidence; return incidence->accept( *this ); } QString result() const { if ( mChanges.isEmpty() ) { return QString(); } QString html = "
    • "; html += mChanges.join( "
    • " ); html += "
      "; return html; } protected: bool visit( Event *event ) { compareEvents( event, dynamic_cast( mExistingIncidence ) ); compareIncidences( event, mExistingIncidence ); return !mChanges.isEmpty(); } bool visit( Todo *todo ) { compareIncidences( todo, mExistingIncidence ); return !mChanges.isEmpty(); } bool visit( Journal *journal ) { compareIncidences( journal, mExistingIncidence ); return !mChanges.isEmpty(); } bool visit( FreeBusy *fb ) { Q_UNUSED( fb ); return !mChanges.isEmpty(); } private: void compareEvents( Event *newEvent, Event *oldEvent ) { if ( !oldEvent || !newEvent ) { return; } if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->allDay() != newEvent->allDay() ) { mChanges += i18n( "The begin of the meeting has been changed from %1 to %2", eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) ); } if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->allDay() != newEvent->allDay() ) { mChanges += i18n( "The end of the meeting has been changed from %1 to %2", eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) ); } } void compareIncidences( Incidence *newInc, Incidence *oldInc ) { if ( !oldInc || !newInc ) { return; } if ( oldInc->summary() != newInc->summary() ) { mChanges += i18n( "The summary has been changed to: \"%1\"", newInc->richSummary() ); } if ( oldInc->location() != newInc->location() ) { mChanges += i18n( "The location has been changed to: \"%1\"", newInc->richLocation() ); } if ( oldInc->description() != newInc->description() ) { mChanges += i18n( "The description has been changed to: \"%1\"", newInc->richDescription() ); } Attendee::List oldAttendees = oldInc->attendees(); Attendee::List newAttendees = newInc->attendees(); for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) { Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() ); if ( !oldAtt ) { mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() ); } else { if ( oldAtt->status() != (*it)->status() ) { mChanges += i18n( "The status of attendee %1 has been changed to: %2", (*it)->fullName(), (*it)->statusStr() ); } } } for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) { Attendee *newAtt = newInc->attendeeByMail( (*it)->email() ); if ( !newAtt ) { mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() ); } } } private: Incidence *mExistingIncidence; QStringList mChanges; }; //@endcond QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text ) { QString res( "%2" ); return res.arg( generateLinkURL( id ) ).arg( text ); return res; } Calendar *InvitationFormatterHelper::calendar() const { return 0; } //@cond PRIVATE // Check if the given incidence is likely one that we own instead one from // a shared calendar (Kolab-specific) static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence ) { CalendarResources* cal = dynamic_cast( calendar ); if ( !cal || !incidence ) { return true; } ResourceCalendar *res = cal->resource( incidence ); if ( !res ) { return true; } const QString subRes = res->subresourceIdentifier( incidence ); if ( !subRes.contains( "/.INBOX.directory/" ) ) { return false; } return true; } static QString formatICalInvitationHelper( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper, bool noHtmlMode ) { if ( invitation.isEmpty() ) { return QString(); } ICalFormat format; // parseScheduleMessage takes the tz from the calendar, // no need to set it manually here for the format! ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation ); if( !msg ) { kDebug() << "Failed to parse the scheduling message"; Q_ASSERT( format.exception() ); kDebug() << format.exception()->message(); return QString(); } IncidenceBase *incBase = msg->event(); incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() ); Incidence *existingIncidence = 0; if ( helper->calendar() ) { existingIncidence = helper->calendar()->incidence( incBase->uid() ); if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) { existingIncidence = 0; } if ( !existingIncidence ) { const Incidence::List list = helper->calendar()->incidences(); for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) { if ( (*it)->schedulingID() == incBase->uid() && incidenceOwnedByMe( helper->calendar(), *it ) ) { existingIncidence = *it; break; } } } } // First make the text of the message QString html; + html += "
      "; + html += ""; - QString tableStyle = QString::fromLatin1( - "style=\"border: solid 1px; margin: 0em;\"" ); - QString tableHead = QString::fromLatin1( - "
      " - "
      " - "" + eventViewerAddTag( "h3", headerVisitor.result() ) + ""; IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode ); if ( !bodyVisitor.act( incBase, msg ) ) { return QString(); } html += bodyVisitor.result(); if ( msg->method() == iTIPRequest ) { // ### Scheduler::Publish/Refresh/Add as well? IncidenceFormatter::IncidenceCompareVisitor compareVisitor; if ( compareVisitor.act( incBase, existingIncidence ) ) { html += i18n( "

      The following changes have been made by the organizer:

      " ); html += compareVisitor.result(); } } - html += "
      "; - html += "
      " ).arg( tableStyle ); - - html += tableHead; IncidenceFormatter::InvitationHeaderVisitor headerVisitor; // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled if ( !headerVisitor.act( incBase, msg ) ) { return QString(); } - html += "

      " + headerVisitor.result() + "

      "; + html += "
      "; - -#if 0 - // TODO: implement this - html += helper->makeLinkURL( "accept", i18n( "[Enter this into my calendar]" ) ); - html += "
       
        "; -#endif - // Add groupware links + html += "

      "; + html += ""; + + //TODO: 4.4, in tdOpen, change border-width:0px to border-width:2px and also + //remove the [] on the button strings: "Accept", "Decline", "Counter", etc. + const QString tdOpen = ""; Incidence *incidence = dynamic_cast( incBase ); switch ( msg->method() ) { case iTIPPublish: case iTIPRequest: case iTIPRefresh: case iTIPAdd: { if ( incidence && incidence->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) { + html += tdOpen; if ( incBase->type() == "Todo" ) { - html += ""; + html += tdClose; } - html += "
      "; + const QString tdClose = ""; + //TODO: 4.4, remove the [] html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) ); } else { - html += ""; html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); } - html += "
      "; + if ( incidence && !existingIncidence ) { // Accept + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "accept", i18nc( "accept to-do request", "[Accept]" ) ); - html += "   "; + html += tdClose; + + // Accept conditionally + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "accept_conditionally", - i18nc( "Accept conditionally", "[Accept cond.]" ) ); - html += "   "; - // counter proposal + i18nc( "Accept conditionally", "[Accept cond.]" ) ); + html += tdClose; + + // Counter proposal + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) ); - html += "   "; + html += tdClose; + // Decline + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "decline", i18nc( "decline to-do request", "[Decline]" ) ); - html += "   "; + html += tdClose; // Delegate + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "delegate", i18nc( "delegate to-do to another", "[Delegate]" ) ); - html += "   "; + html += tdClose; // Forward + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "forward", i18nc( "forward request to another", "[Forward]" ) ); + html += tdClose; + // Check in calendar if ( incBase->type() == "Event" ) { - html += "   "; + html += tdOpen; + //TODO: 4.4, remove the [] + //TODO: 4.4, change to "Check calendar" html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) ); + html += tdClose; } } break; } case iTIPCancel: // Cancel event from my calendar + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) ); + html += tdClose; break; case iTIPReply: // Enter this into my calendar + html += tdOpen; + //TODO: 4.4, remove the [] + //TODO: 4.4, change string to "Enter this response into my..." if ( incBase->type() == "Todo" ) { html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) ); } else { html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); } + html += tdClose; break; case iTIPCounter: + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) ); - html += " "; + html += tdClose; + + html += tdOpen; + //TODO: 4.4, remove the [] html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) ); - html += " "; + html += tdClose; + + html += tdOpen; + //TODO: 4.4, remove the [] + //TODO: 4.4, change string to "Check calendar" html += helper->makeLink( "check_calendar", i18n( "[Check my calendar]" ) ); + html += tdClose; break; case iTIPDeclineCounter: case iTIPNoMethod: break; } - html += "
      "; - - html += "


      "; + // close the groupware table + html += ""; + // close the top-level table + html += ""; + kDebug() << html; return html; } //@endcond QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper ) { return formatICalInvitationHelper( invitation, mCalendar, helper, false ); } QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper ) { return formatICalInvitationHelper( invitation, mCalendar, helper, true ); } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ //@cond PRIVATE class KCal::IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor { public: ToolTipVisitor() : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( IncidenceBase *incidence, bool richText=true, KDateTime::Spec spec=KDateTime::Spec() ) { mRichText = richText; mSpec = spec; mResult = ""; return incidence ? incidence->accept( *this ) : false; } QString result() const { return mResult; } protected: bool visit( Event *event ); bool visit( Todo *todo ); bool visit( Journal *journal ); bool visit( FreeBusy *fb ); QString dateRangeText( Event *event ); QString dateRangeText( Todo *todo ); QString dateRangeText( Journal *journal ); QString dateRangeText( FreeBusy *fb ); QString generateToolTip( Incidence *incidence, QString dtRangeText ); protected: bool mRichText; KDateTime::Spec mSpec; QString mResult; }; QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event ) { //FIXME: support mRichText==false QString ret; QString tmp; if ( event->isMultiDay() ) { tmp = IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ); ret += "
      " + i18nc( "Event start", "From: %1", tmp ); tmp = IncidenceFormatter::dateToString( event->dtEnd(), true, mSpec ); ret += "
      " + i18nc( "Event end","To: %1", tmp ); } else { ret += "
      " + i18n( "Date: %1", IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ) ); if ( !event->allDay() ) { const QString dtStartTime = IncidenceFormatter::timeToString( event->dtStart(), true, mSpec ); const QString dtEndTime = IncidenceFormatter::timeToString( event->dtEnd(), true, mSpec ); if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00' tmp = "
      " + i18nc( "time for event", "Time: %1", dtStartTime ); } else { tmp = "
      " + i18nc( "time range for event", "Time: %1 - %2", dtStartTime, dtEndTime ); } ret += tmp; } } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo ) { //FIXME: support mRichText==false QString ret; if ( todo->hasStartDate() && todo->dtStart().isValid() ) { // No need to add here. This is separated issue and each line // is very visible on its own. On the other hand... Yes, I like it // italics here :) ret += "
      " + i18n( "Start: %1", IncidenceFormatter::dateToString( todo->dtStart( false ), true, mSpec ) ); } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { ret += "
      " + i18n( "Due: %1", IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay(), true, mSpec ) ); } if ( todo->isCompleted() ) { ret += "
      " + i18n( "Completed: %1", todo->completedStr() ); } else { ret += "
      " + i18nc( "percent complete", "%1 % completed", todo->percentComplete() ); } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal ) { //FIXME: support mRichText==false QString ret; if ( journal->dtStart().isValid() ) { ret += "
      " + i18n( "Date: %1", IncidenceFormatter::dateToString( journal->dtStart(), false, mSpec ) ); } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb ) { //FIXME: support mRichText==false QString ret; ret = "
      " + i18n( "Period start: %1", KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) ); ret += "
      " + i18n( "Period start: %1", KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) ); return ret.replace( ' ', " " ); } bool IncidenceFormatter::ToolTipVisitor::visit( Event *event ) { mResult = generateToolTip( event, dateRangeText( event ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo ) { mResult = generateToolTip( todo, dateRangeText( todo ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal ) { mResult = generateToolTip( journal, dateRangeText( journal ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb ) { //FIXME: support mRichText==false mResult = "" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + ""; mResult += dateRangeText( fb ); mResult += ""; return !mResult.isEmpty(); } QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence, QString dtRangeText ) { //FIXME: support mRichText==false if ( !incidence ) { return QString(); } QString tmp = ""+ incidence->richSummary() + ""; tmp += dtRangeText; if ( !incidence->location().isEmpty() ) { // Put Location: in italics tmp += "
      " + i18n( "Location: %1", incidence->richLocation() ); } if ( !incidence->description().isEmpty() ) { QString desc( incidence->description() ); if ( !incidence->descriptionIsRich() ) { if ( desc.length() > 120 ) { desc = desc.left( 120 ) + "..."; } desc = Qt::escape( desc ).replace( '\n', "
      " ); } else { // TODO: truncate the description when it's rich text } tmp += "
      ----------
      " + i18n( "Description:" ) + "
      " + desc; } tmp += "
      "; return tmp; } //@endcond QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText ) { return toolTipStr( incidence, richText, KDateTime::Spec() ); } QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence, bool richText, KDateTime::Spec spec ) { ToolTipVisitor v; if ( v.act( incidence, richText, spec ) ) { return v.result(); } else { return QString(); } } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ //@cond PRIVATE static QString mailBodyIncidence( Incidence *incidence ) { QString body; if ( !incidence->summary().isEmpty() ) { body += i18n( "Summary: %1\n", incidence->richSummary() ); } if ( !incidence->organizer().isEmpty() ) { body += i18n( "Organizer: %1\n", incidence->organizer().fullName() ); } if ( !incidence->location().isEmpty() ) { body += i18n( "Location: %1\n", incidence->richLocation() ); } return body; } //@endcond //@cond PRIVATE class KCal::IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor { public: MailBodyVisitor() : mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() ) { mSpec = spec; mResult = ""; return incidence ? incidence->accept( *this ) : false; } QString result() const { return mResult; } protected: bool visit( Event *event ); bool visit( Todo *todo ); bool visit( Journal *journal ); bool visit( FreeBusy * ) { mResult = i18n( "This is a Free Busy Object" ); return !mResult.isEmpty(); } protected: KDateTime::Spec mSpec; QString mResult; }; bool IncidenceFormatter::MailBodyVisitor::visit( Event *event ) { QString recurrence[]= { i18nc( "no recurrence", "None" ), i18nc( "event recurs by minutes", "Minutely" ), i18nc( "event recurs by hours", "Hourly" ), i18nc( "event recurs by days", "Daily" ), i18nc( "event recurs by weeks", "Weekly" ), i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ), i18nc( "event recurs same day each month", "Monthly Same Day" ), i18nc( "event recurs same month each year", "Yearly Same Month" ), i18nc( "event recurs same day each year", "Yearly Same Day" ), i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" ) }; mResult = mailBodyIncidence( event ); mResult += i18n( "Start Date: %1\n", IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ) ); if ( !event->allDay() ) { mResult += i18n( "Start Time: %1\n", IncidenceFormatter::timeToString( event->dtStart(), true, mSpec ) ); } if ( event->dtStart() != event->dtEnd() ) { mResult += i18n( "End Date: %1\n", IncidenceFormatter::dateToString( event->dtEnd(), true, mSpec ) ); } if ( !event->allDay() ) { mResult += i18n( "End Time: %1\n", IncidenceFormatter::timeToString( event->dtEnd(), true, mSpec ) ); } if ( event->recurs() ) { Recurrence *recur = event->recurrence(); // TODO: Merge these two to one of the form "Recurs every 3 days" mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] ); mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() ); if ( recur->duration() > 0 ) { mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() ); mResult += '\n'; } else { if ( recur->duration() != -1 ) { // TODO_Recurrence: What to do with all-day QString endstr; if ( event->allDay() ) { endstr = KGlobal::locale()->formatDate( recur->endDate() ); } else { endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() ); } mResult += i18n( "Repeat until: %1\n", endstr ); } else { mResult += i18n( "Repeats forever\n" ); } } } QString details = event->richDescription(); if ( !details.isEmpty() ) { mResult += i18n( "Details:\n%1\n", details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo ) { mResult = mailBodyIncidence( todo ); if ( todo->hasStartDate() && todo->dtStart().isValid() ) { mResult += i18n( "Start Date: %1\n", IncidenceFormatter::dateToString( todo->dtStart(false), true, mSpec ) ); if ( !todo->allDay() ) { mResult += i18n( "Start Time: %1\n", IncidenceFormatter::timeToString( todo->dtStart(false), true, mSpec ) ); } } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { mResult += i18n( "Due Date: %1\n", IncidenceFormatter::dateToString( todo->dtDue(), true, mSpec ) ); if ( !todo->allDay() ) { mResult += i18n( "Due Time: %1\n", IncidenceFormatter::timeToString( todo->dtDue(), true, mSpec ) ); } } QString details = todo->richDescription(); if ( !details.isEmpty() ) { mResult += i18n( "Details:\n%1\n", details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal ) { mResult = mailBodyIncidence( journal ); mResult += i18n( "Date: %1\n", IncidenceFormatter::dateToString( journal->dtStart(), true, mSpec ) ); if ( !journal->allDay() ) { mResult += i18n( "Time: %1\n", IncidenceFormatter::timeToString( journal->dtStart(), true, mSpec ) ); } if ( !journal->description().isEmpty() ) { mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() ); } return !mResult.isEmpty(); } //@endcond QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence ) { return mailBodyStr( incidence, KDateTime::Spec() ); } QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } MailBodyVisitor v; if ( v.act( incidence, spec ) ) { return v.result(); } return QString(); } //@cond PRIVATE static QString recurEnd( Incidence *incidence ) { QString endstr; if ( incidence->allDay() ) { endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() ); } else { endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() ); } return endstr; } //@endcond QString IncidenceFormatter::recurrenceString( Incidence *incidence ) { if ( !incidence->recurs() ) { return i18n( "No recurrence" ); } QStringList dayList; dayList.append( i18n( "31st Last" ) ); dayList.append( i18n( "30th Last" ) ); dayList.append( i18n( "29th Last" ) ); dayList.append( i18n( "28th Last" ) ); dayList.append( i18n( "27th Last" ) ); dayList.append( i18n( "26th Last" ) ); dayList.append( i18n( "25th Last" ) ); dayList.append( i18n( "24th Last" ) ); dayList.append( i18n( "23rd Last" ) ); dayList.append( i18n( "22nd Last" ) ); dayList.append( i18n( "21st Last" ) ); dayList.append( i18n( "20th Last" ) ); dayList.append( i18n( "19th Last" ) ); dayList.append( i18n( "18th Last" ) ); dayList.append( i18n( "17th Last" ) ); dayList.append( i18n( "16th Last" ) ); dayList.append( i18n( "15th Last" ) ); dayList.append( i18n( "14th Last" ) ); dayList.append( i18n( "13th Last" ) ); dayList.append( i18n( "12th Last" ) ); dayList.append( i18n( "11th Last" ) ); dayList.append( i18n( "10th Last" ) ); dayList.append( i18n( "9th Last" ) ); dayList.append( i18n( "8th Last" ) ); dayList.append( i18n( "7th Last" ) ); dayList.append( i18n( "6th Last" ) ); dayList.append( i18n( "5th Last" ) ); dayList.append( i18n( "4th Last" ) ); dayList.append( i18n( "3rd Last" ) ); dayList.append( i18n( "2nd Last" ) ); dayList.append( i18nc( "last day of the month", "Last" ) ); dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI dayList.append( i18n( "1st" ) ); dayList.append( i18n( "2nd" ) ); dayList.append( i18n( "3rd" ) ); dayList.append( i18n( "4th" ) ); dayList.append( i18n( "5th" ) ); dayList.append( i18n( "6th" ) ); dayList.append( i18n( "7th" ) ); dayList.append( i18n( "8th" ) ); dayList.append( i18n( "9th" ) ); dayList.append( i18n( "10th" ) ); dayList.append( i18n( "11th" ) ); dayList.append( i18n( "12th" ) ); dayList.append( i18n( "13th" ) ); dayList.append( i18n( "14th" ) ); dayList.append( i18n( "15th" ) ); dayList.append( i18n( "16th" ) ); dayList.append( i18n( "17th" ) ); dayList.append( i18n( "18th" ) ); dayList.append( i18n( "19th" ) ); dayList.append( i18n( "20th" ) ); dayList.append( i18n( "21st" ) ); dayList.append( i18n( "22nd" ) ); dayList.append( i18n( "23rd" ) ); dayList.append( i18n( "24th" ) ); dayList.append( i18n( "25th" ) ); dayList.append( i18n( "26th" ) ); dayList.append( i18n( "27th" ) ); dayList.append( i18n( "28th" ) ); dayList.append( i18n( "29th" ) ); dayList.append( i18n( "30th" ) ); dayList.append( i18n( "31st" ) ); int weekStart = KGlobal::locale()->weekStartDay(); QString dayNames; QString txt; const KCalendarSystem *calSys = KGlobal::locale()->calendar(); Recurrence *recur = incidence->recurrence(); switch ( recur->recurrenceType() ) { case Recurrence::rNone: return i18n( "No recurrence" ); case Recurrence::rMinutely: if ( recur->duration() != -1 ) { txt = i18np( "Recurs every minute until %2", "Recurs every %1 minutes until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs every minute", "Recurs every %1 minutes", recur->frequency() ); case Recurrence::rHourly: if ( recur->duration() != -1 ) { txt = i18np( "Recurs hourly until %2", "Recurs every %1 hours until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() ); case Recurrence::rDaily: if ( recur->duration() != -1 ) { txt = i18np( "Recurs daily until %2", "Recurs every %1 days until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() ); case Recurrence::rWeekly: { bool addSpace = false; for ( int i = 0; i < 7; ++i ) { if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) { if ( addSpace ) { dayNames.append( i18nc( "separator for list of days", ", " ) ); } dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, KCalendarSystem::ShortDayName ) ); addSpace = true; } } if ( dayNames.isEmpty() ) { dayNames = i18nc( "Recurs weekly on no days", "no days" ); } if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs weekly on [list of days] until end-date", "Recurs weekly on %2 until %3", "Recurs every %1 weeks on %2 until %3", recur->frequency(), dayNames, recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs weekly on [list of days]", "Recurs weekly on %2", "Recurs every %1 weeks on %2", recur->frequency(), dayNames ); } case Recurrence::rMonthlyPos: { KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]" " weekdayname until end-date", "Recurs every month on the %2 %3 until %4", "Recurs every %1 months on the %2 %3 until %4", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(),KCalendarSystem::LongDayName ), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname", "Recurs every month on the %2 %3", "Recurs every %1 months on the %2 %3", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) ); } case Recurrence::rMonthlyDay: { int days = recur->monthDays()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date", "Recurs monthly on the %2 day until %3", "Recurs every %1 months on the %2 day until %3", recur->frequency(), dayList[days + 31], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs monthly on the [1st|2nd|...] day", "Recurs monthly on the %2 day", "Recurs every %1 month on the %2 day", recur->frequency(), dayList[days + 31] ); } case Recurrence::rYearlyMonth: { if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]" " until end-date", "Recurs yearly on %2 %3 until %4", "Recurs every %1 years on %2 %3 until %4", recur->frequency(), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->yearDates()[0] + 31 ], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } if ( !recur->yearDates().isEmpty() ) { return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]", "Recurs yearly on %2 %3", "Recurs every %1 years on %2 %3", recur->frequency(), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->yearDates()[0] + 31 ] ); } else { if (!recur->yearMonths().isEmpty() ) { return i18nc( "Recurs Every year on month-name [1st|2nd|...]", "Recurs yearly on %1 %2", calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->startDate().day() + 31 ] ); } else { return i18nc( "Recurs Every year on month-name [1st|2nd|...]", "Recurs yearly on %1 %2", calSys->monthName( recur->startDate().month(), recur->startDate().year() ), dayList[ recur->startDate().day() + 31 ] ); } } } case Recurrence::rYearlyDay: if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs every N years on day N until end-date", "Recurs every year on day %2 until %3", "Recurs every %1 years" " on day %2 until %3", recur->frequency(), recur->yearDays()[0], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs every N YEAR[S] on day N", "Recurs every year on day %2", "Recurs every %1 years" " on day %2", recur->frequency(), recur->yearDays()[0] ); case Recurrence::rYearlyPos: { KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " "of monthname until end-date", "Every year on the %2 %3 of %4 until %5", "Every %1 years on the %2 %3 of %4" " until %5", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " "of monthname", "Every year on the %2 %3 of %4", "Every %1 years on the %2 %3 of %4", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ); } default: return i18n( "Incidence recurs" ); } } QString IncidenceFormatter::timeToString( const KDateTime &date, bool shortfmt, const KDateTime::Spec &spec ) { if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone; } else { return KGlobal::locale()->formatTime( date.time(), !shortfmt ); } } QString IncidenceFormatter::dateToString( const KDateTime &date, bool shortfmt, const KDateTime::Spec &spec ) { if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; } else { return KGlobal::locale()->formatDate( date.date(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); } } QString IncidenceFormatter::dateTimeToString( const KDateTime &date, bool allDay, bool shortfmt, const KDateTime::Spec &spec ) { if ( allDay ) { return dateToString( date, shortfmt, spec ); } if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatDateTime( date.toTimeSpec( spec ).dateTime(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; } else { return KGlobal::locale()->formatDateTime( date.dateTime(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); } } diff --git a/kcal/kcal_manager.desktop b/kcal/kcal_manager.desktop index d01f2a4e3..d76b4f83e 100644 --- a/kcal/kcal_manager.desktop +++ b/kcal/kcal_manager.desktop @@ -1,45 +1,46 @@ # KDE Config File [Desktop Entry] Name=Calendar Name[be]=Каляндар Name[ca]=Calendari Name[cs]=Kalendář Name[da]=Kalender Name[de]=Kalender Name[el]=Ημερολόγιο Name[es]=Calendario Name[et]=Kalender Name[fr]=Calendrier Name[ga]=Féilire Name[gl]=Axenda Name[hne]=कलेन्डर Name[hu]=Naptár Name[it]=Calendario Name[ja]=カレンダー Name[km]=ប្រតិទិន Name[lt]=Kalendorius Name[lv]=Kalendārs Name[nb]=Kalender Name[nds]=Kalenner Name[nl]=Agenda Name[nn]=Kalender Name[oc]=Calendièr Name[pa]=ਕੈਲੰਡਰ Name[pl]=Kalendarz Name[pt]=Calendário Name[pt_BR]=Calendário Name[ru]=Календарь Name[se]=Kaleandar +Name[sk]=Kalendár Name[sl]=Koledar Name[sr]=Календар Name[sr@latin]=Kalendar Name[sv]=Kalender Name[th]=ปฎิทิน Name[tr]=Takvim Name[uk]=Календар Name[x-test]=xxCalendarxx Name[zh_CN]=日历 Name[zh_TW]=行事曆 Type=Service X-KDE-ServiceTypes=KResources/Manager X-KDE-ResourceFamily=calendar diff --git a/kcal/local.desktop b/kcal/local.desktop index 2e9ca5c5b..a6abed54b 100644 --- a/kcal/local.desktop +++ b/kcal/local.desktop @@ -1,82 +1,84 @@ [Desktop Entry] Name=Calendar in Local File Name[be]=Каляндар у мясцовым файлу Name[ca]=Calendari en un fitxer local Name[cs]=Kalendář v místním souboru Name[da]=Kalender i lokal fil Name[de]=Kalender in lokaler Datei Name[el]=Ημερολόγιο σε τοπικό αρχείο Name[es]=Calendario en archivo local Name[et]=Kalender kohalikus failis Name[fr]=Calendrier dans un fichier local Name[ga]=Féilire i gComhad Logánta Name[gl]=Axenda nun ficheiro local Name[hne]=लोकल फाइल मं कलेन्डर Name[hu]=Helyi naptárfájl Name[it]=Calendario in file locale Name[ja]=ローカルファイルのカレンダー Name[km]=ប្រតិទិន​ក្នុង​ឯកសារ​មូលដ្ឋាន Name[lt]=Kalendorius vietiniame faile Name[lv]=Kalendārs lokālā failā Name[nb]=Kalender i lokal fil Name[nds]=Kalenner in en lokale Datei Name[nl]=Agenda in lokaal bestand Name[nn]=Kalender i lokal fil Name[pa]=ਲੋਕਲ ਫਾਇਲ 'ਚ ਕੈਲੰਡਰ Name[pl]=Kalendarz w pliku lokalnym Name[pt]=Calendário num Ficheiro Local Name[pt_BR]=Calendário em arquivo local Name[ro]=Calendar în fișier local Name[ru]=Календарь в локальном файле Name[se]=Kaleandar báikkálaš fiillas +Name[sk]=Kalendár v lokálnom súbore Name[sl]=Koledar v krajevni datoteki Name[sr]=Календар у локалном фајлу Name[sr@latin]=Kalendar u lokalnom fajlu Name[sv]=Kalender i lokal fil Name[th]=ปฎิทินในแฟ้มภายในระบบ Name[tr]=Yerel Dosyadaki Takvim Name[uk]=Календар в локальному файлі Name[x-test]=xxCalendar in Local Filexx Name[zh_CN]=本地文件中的日历 Name[zh_TW]=本地端檔案裡的行事曆 Comment=Provides access to a calendar stored in a single local file Comment[ca]=Proporciona l'accés a un calendari emmagatzemat en un fitxer local individual Comment[cs]=Poskytuje přístup ke kalendáři uloženém v jednom lokálním souboru Comment[da]=Giver adgang til en kalender lagret i en enkelt lokal fil Comment[de]=Ermöglicht Zugriff auf einen Kalender, der in einer einzigen Dateien lokal gespeichert ist Comment[el]=Προσφέρει πρόσβαση σε ένα ημερολόγιο αποθηκευμένο σε ένα τοπικό αρχείο Comment[es]=Provee acceso a un calendario almacenado en un único archivo local Comment[et]=Võimaldab kasutada kohalikku faili salvestatud kalendrit Comment[fr]=Fourni l'accès à un agenda stocké dans un fichier local Comment[ga]=Soláthraíonn sé seo rochtain ar fhéilire stóráilte i gcomhad logánta amháin Comment[gl]=Dá acceso a un calendario gardado nun único ficheiro local Comment[hu]=Helyi fájlban tárolt naptár elérését biztosítja. Comment[it]=Fornisce accesso ad un calendario memorizzato in un singolo file locale Comment[ja]=単一のローカルファイルに保存されているカレンダーへのアクセスを提供します。 Comment[km]=ផ្ដល់​ការ​ចូលដំណើរការ​ទៅកាន់​ប្រតិទិន​ដែល​បានផ្ទុក​នៅ​ក្នុង​​ឯកសារ​មូលដ្ឋាន​តែ​មួយ Comment[lt]=Suteikia prieigą prie kalendoriaus, esančio viename vietiniame faile Comment[lv]=Nodrošina piekļuvi kalendāram, kas glabājas vienā lokālā failā Comment[nb]=Gir tilgang til en kalender lagret i en enkelt, lokal fil Comment[nds]=Stellt Togriep op en Kalenner binnen een enkel lokaal Datei praat. Comment[nl]=Geeft toegang naar een agenda, die in een enkel lokaal bestand opgeslagen is Comment[nn]=Gjev tilgang til kontaktar lagra i ei einskild lokal fil. Comment[pl]=Daje dostęp do kalendarza przechowywanego w pliku lokalnym Comment[pt]=Oferece o acesso a um calendário guardado num único ficheiro local Comment[pt_BR]=Fornece acesso a um calendário armazenado em um único arquivo local Comment[ro]=Oferă acces la un calendar stocat într-un singur fișier local Comment[ru]=Хранение календаря в одном локальном файле Comment[se]=Dáinna beasat kaleandarii vurkejuvvon okta báikásaš fiilan +Comment[sk]=Poskytuje prístup ku kalendáru uloženému v jednoduchom lokálnom súbore. Comment[sl]=Nudi dostop do koledarja, ki je shranjen v eni sami krajevni datoteki Comment[sr]=Пружа приступ календару складиштеном у једном локалном фајлу Comment[sr@latin]=Pruža pristup kalendaru skladištenom u jednom lokalnom fajlu Comment[sv]=Ger tillgång till en kalender lagrad i en enda lokal fil Comment[tr]=TEk bir yerel dosyada depolanmış bir takvime erişim sağlar Comment[uk]=Надає доступ до календаря, що зберігається у окремому локальному файлі Comment[x-test]=xxProvides access to a calendar stored in a single local filexx Comment[zh_CN]=提供对被存储在单独的本地文件中的日历的访问支持 Comment[zh_TW]=提供存取儲存在本地檔案中的行事曆的功能 X-KDE-Library=kcal_local Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=calendar X-KDE-ResourceType=file diff --git a/kcal/localdir.desktop b/kcal/localdir.desktop index 8967569d8..3dbcda227 100644 --- a/kcal/localdir.desktop +++ b/kcal/localdir.desktop @@ -1,82 +1,84 @@ [Desktop Entry] Name=Calendar in Local Directory Name[be]=Каляндар у мясцовай тэчцы Name[ca]=Calendari en un directori local Name[cs]=Kalendář v místním adresáři Name[da]=Kalender i lokal mappe Name[de]=Kalender in lokalem Ordner Name[el]=Ημερολόγιο σε τοπικό κατάλογο Name[es]=Calendario en directorio local Name[et]=Kalender kohalikus kataloogis Name[fr]=Calendrier dans un dossier local Name[ga]=Féilire i gComhadlann Logánta Name[gl]=Axenda nun cartafol local Name[hne]=लोकल डिरेक्टरी मं कलेन्डर Name[hu]=Naptár helyi könyvtárban Name[it]=Calendario nella cartella locale Name[ja]=ローカルディレクトリのカレンダー Name[km]=ប្រតិទិន​ក្នុង​ថត​មូលដ្ឋាន Name[lt]=Kalendorius vietiniame aplanke Name[lv]=Kalendārs lokālā mapē Name[nb]=Kalender i lokal mappe Name[nds]=Kalenner in'n lokalen Orner Name[nl]=Agenda in lokale map Name[nn]=Kalender i lokal mappe Name[pa]=ਲੋਕਲ ਡਾਇਰੈਕਟਰੀ 'ਚ ਕੈਲੰਡਰ Name[pl]=Kalendarz w katalogu lokalnym Name[pt]=Calendário numa Pasta Local Name[pt_BR]=Calendário em pasta local Name[ro]=Calendar în director local Name[ru]=Календарь в локальной папке Name[se]=Kaleandar báikkálaš katalogas +Name[sk]=Kalendár v lokálnom adresári. Name[sl]=Koledar v krajevnem imeniku Name[sr]=Календар у локалној фасцикли Name[sr@latin]=Kalendar u lokalnoj fascikli Name[sv]=Kalender i lokal katalog Name[th]=ปฎิทินในไดเรกทอรีภายในระบบ Name[tr]=Yerel Dizindeki Takvim Name[uk]=Календар в локальній теці Name[x-test]=xxCalendar in Local Directoryxx Name[zh_CN]=本地目录中的日历 Name[zh_TW]=本地端目錄裡的行事曆 Comment=Provides access to calendar items, each stored in a single file, in a given directory Comment[ca]=Proporciona l'accés als elements d'un calendari, cada un emmagatzemat en un fitxer individual, en un directori donat Comment[cs]=Poskytuje přístup k položkám kalendáře, každé uložené v jednom souboru v daném adresáři Comment[da]=Giver adgang til kalenderelementer, hver lagret i en enkelt fil, i en given mappe -Comment[de]=Ermöglicht Zugriff auf Kalender, die jeweils in einzelnen Dateien in einem vorgegebenen Ordner gespeichert sind. +Comment[de]=Ermöglicht Zugriff auf Kalender, die jeweils in einzelnen Dateien in einem vorgegebenen Ordner gespeichert sind Comment[el]=Προσφέρει πρόσβαση σε αντικείμενα ημερολογίου, αποθηκευμένα σε ξεχωριστά αρχεία, σε ένα
δοσμένο κατάλογο Comment[es]=Provee acceso a elementos de un calendario, cada uno almacenado en un archivo diferente, dentro de un directorio determinado Comment[et]=Võimaldab kasutada eraldi failidesse salvestatud kalendrielemente määratud kataloogis Comment[fr]=Fourni l'accès à des entrées d'agenda, chaque élément étant représenté par un fichier dans le dossier indiqué Comment[ga]=Soláthraíonn sé seo rochtain ar mhíreanna féilire, gach ceann stóráilte i gcomhad aonair, i gcomhadlann sonraithe Comment[gl]=Dá acceso aos obxectos do calendario, cada un gardado nun ficheiro nun cartafol dado Comment[hu]=Helyi mappában tárolt naptérbejegyzések elérését biztosítja. Minden bejegyzés külön fájlban található. Comment[it]=Fornisce accesso a voci di calendario, ciascuna memorizzata in un singolo file in una data cartella Comment[ja]=それぞれが単一のファイルに保存されている任意のディレクトリ内のカレンダーアイテムへのアクセスを提供します。 Comment[km]=ផ្ដល់​ការ​ចូលដំណើរការ​ទៅ​កាន់​ធាតុ​ប្រតិទិន ធាតុ​នីមួយៗ​បាន​ផ្ទុក​នៅ​ក្នុង​ឯកសារ​តែ​មួយ នៅ​ក្នុង​ថត​ដែល​បាន​ផ្ដល់​ឲ្យ Comment[lt]=Suteikia prieigą prie kalendoriaus įrašų, kurie saugomi individualiai nurodytame aplanke Comment[lv]=Nodrošina piekļuvi kalendāra ierakstiem, kas katrs glabājas vienā failā, norādītā mapē Comment[nb]=Gir tilgang til kalenderoppføringer, hver lagret i en enkelt fil, i en gitt mappe Comment[nds]=Stellt Togriep op Kalennerindrääg praat, elkeen binnen een Datei binnen en angeven Orner. Comment[nl]=Geeft toegang tot agenda-items, elk opgeslagen in een enkel bestand in een opgegeven map Comment[nn]=Gjev tilgang til kalenderelement i ei viss mappe, der dei er lagra i kvar sine filer. Comment[pl]=Daje dostęp do plików obiektów kalendarza, każdy w oddzielnym pliku, w podanym katalogu Comment[pt]=Oferece o acesso aos itens do calendário, estando cada um guardado num único ficheiro de uma dada pasta Comment[pt_BR]=Fornece acesso aos itens do calendário, cada um armazenado em um único arquivo na pasta indicada Comment[ro]=Oferă acces la elemente de calendar, fiecare stocat într-un singur fișier, într-un anumit director Comment[ru]=Хранение каждого события или задачи в отдельных файлах в указанной локальной папке. Comment[se]=Dáinna beasat kaleandarmerkošidda, vurkejuvvon okta fiilan, dihto katalogas +Comment[sk]=Poskytuje prístup do položiek adresára, každá uložená v jednoduchom súbore v danom adresári. Comment[sl]=Nudi dostop do koledarskih vnosov, ki so shranjeni vsak posebej v svoji datoteki, v danem imeniku Comment[sr]=Пружа приступ календарским ставкама, складиштеним у појединачним фајловима у датој фасцикли. Comment[sr@latin]=Pruža pristup kalendarskim stavkama, skladištenim u pojedinačnim fajlovima u datoj fascikli. Comment[sv]=Ger tillgång till kalenderobjekt, vart och ett lagrat i en enskild lokal fil, i en given katalog Comment[tr]=Belirli bir dizinde, her biri tek bir dosyada depolanmış takvim ögelerine erişim sağlar Comment[uk]=Надає доступ до записів календаря, кожен з яких зберігається у окремому файлі у вказаному каталозі Comment[x-test]=xxProvides access to calendar items, each stored in a single file, in a given directoryxx Comment[zh_CN]=提供对被存储在给定目录下单独文件中的日历内容的访问支持 Comment[zh_TW]=提供存取在特定目錄下,個別儲存於單一檔案中的行事曆的項目的功能 X-KDE-Library=kcal_localdir Type=Service X-KDE-ServiceTypes=KResources/Plugin X-KDE-ResourceFamily=calendar X-KDE-ResourceType=localdir diff --git a/kioslave/sieve/sieve.protocol b/kioslave/sieve/sieve.protocol index 37141af5d..75a1c668c 100644 --- a/kioslave/sieve/sieve.protocol +++ b/kioslave/sieve/sieve.protocol @@ -1,50 +1,51 @@ [Protocol] exec=kio_sieve protocol=sieve input=none output=filesystem listing=Name,Access,Type,MimeType, reading=true writing=true makedir=false deleting=true linking=false moving=false Icon=view-filter Description=An ioslave for the Sieve mail filtering protocol Description[ca]=Un «ioslave» pel protocol de filtrat de correu Sieve Description[cs]=Protokol pro filtrování pošty Sieve Description[da]=En ioslave til mail-filtreringsprotokollen Sieve Description[de]=Ein Ein-/Ausgabemodul für das Mailfilter-Protokoll Sieve Description[el]=Ένα ioslave για το πρωτόκολλο φιλτραρίσματος αλληλογραφίας Sieve Description[es]=Un esclavo de E/S para el protocolo de filtrado de correo Sieve Description[et]=Sieve kirjade filtreerimise protokolli IO-moodul Description[fr]=Un module d'entrée / sortie pour le protocole de filtrage de messagerie Sieve Description[ga]=Sclábhaí I/A le haghaidh prótacail scagtha ríomhphoist Sieve Description[gl]=Un ioslave para o protocolo de filtrado de correo Sieve Description[hne]=सीव मेल फिल्टरिंग प्रोटोकाल बर आईओस्लेव Description[hu]=KDE-protokoll a Sieve levélszűrő protokollhoz Description[it]=Un IOSlave per il protocollo di filtraggio della posta Sieve Description[ja]=Sieve メールフィルタリングプロトコルのための ioslave Description[km]=ioslave សម្រាប់​ពិធីការ​តម្រង​សំបុត្រ​របស់ Sieve Description[lv]=IO apstrādātājs Sieve pasta filtrēšanas protokolam Description[nb]=En ioslave for Sieve e-postfiltrering Description[nds]=In-/Utgaavmoduul för't Nettpostfilter-Protokoll Sieve Description[nl]=Een KIO-slave voor het Sieve-mail filterprotocol Description[nn]=Ein IO-slave for e-postfiltreringsprotokollen Sieve Description[pl]=Moduł ioslave dla protokołu filtrowania poczty Sieve Description[pt]=Um 'ioslave' para o protocolo de filtragem de correio Sieve Description[pt_BR]=Um ioslave para o protocolo de filtragem de e-mail Sieve Description[ro]=Un „ioslave” pentru protocolul de filtrare a poștei Sieve Description[ru]=Поддержка протокола фильтрации почты Sieve Description[se]=SO-šláva Sieve e-boastasillenprotokolla várás +Description[sk]=Ioslave pre Sieve mail filtrovací protokol Description[sl]=Ioslave za protokol poštnega filtriranja Sieve Description[sr]=У/И захват за сито, протокол филтрирања поште Description[sr@latin]=U/I zahvat za sito, protokol filtriranja pošte Description[sv]=En I/O-slav för brevfiltreringsprotokollet Sieve Description[th]=ส่วนเสริม ioslave สำหรับโพรโทคอลการกรองจดหมาย Sieve Description[tr]=Sieve e-posta filtrelemesi için bir kioslave Description[uk]=Підлеглий В/В для протоколу фільтрування пошти Sieve Description[x-test]=xxAn ioslave for the Sieve mail filtering protocolxx Description[zh_CN]=Sieve 邮件过滤协议的 ioslave Description[zh_TW]=Sieve 郵件過濾協定的 ioslave diff --git a/kmime/kmime_dateformatter.cpp b/kmime/kmime_dateformatter.cpp index 296dba9e6..93454e4b9 100644 --- a/kmime/kmime_dateformatter.cpp +++ b/kmime/kmime_dateformatter.cpp @@ -1,336 +1,336 @@ /* kmime_dateformatter.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the DateFormatter class. @brief Defines the DateFormatter class. @authors the KMime authors (see AUTHORS file) */ #include "kmime_dateformatter.h" #include #include // for abs() #include #include #include #include using namespace KMime; //@cond PRIVATE int DateFormatter::mDaylight = -1; //@endcond DateFormatter::DateFormatter( FormatType ftype ) : mFormat( ftype ), mTodayOneSecondBeforeMidnight( 0 ) { } DateFormatter::~DateFormatter() { } DateFormatter::FormatType DateFormatter::format() const { return mFormat; } void DateFormatter::setFormat( FormatType ftype ) { mFormat = ftype; } QString DateFormatter::dateString( time_t t , const QString &lang , bool shortFormat, bool includeSecs ) const { switch ( mFormat ) { case Fancy: return fancy( t ); break; case Localized: return localized( t, shortFormat, includeSecs, lang ); break; case CTime: return cTime( t ); break; case Iso: return isoDate( t ); break; case Rfc: return rfc2822( t ); break; case Custom: return custom( t ); break; } return QString(); } QString DateFormatter::dateString( const QDateTime &dt, const QString &lang, bool shortFormat, bool includeSecs ) const { return dateString( qdateToTimeT( dt ), lang, shortFormat, includeSecs ); } QString DateFormatter::rfc2822( time_t t ) const { QDateTime tmp; QString ret; tmp.setTime_t( t ); ret = tmp.toString( "ddd, dd MMM yyyy hh:mm:ss " ).toLatin1(); ret += zone( t ); return ret; } QString DateFormatter::custom( time_t t ) const { if ( mCustomFormat.isEmpty() ) { return QString(); } int z = mCustomFormat.indexOf( 'Z' ); QDateTime d; QString ret = mCustomFormat; d.setTime_t( t ); if ( z != -1 ) { ret.replace( z, 1, zone( t ) ); } ret = d.toString( ret ); return ret; } void DateFormatter::setCustomFormat( const QString &format ) { mCustomFormat = format; mFormat = Custom; } QString DateFormatter::customFormat() const { return mCustomFormat; } QByteArray DateFormatter::zone( time_t t ) const { #if defined(HAVE_TIMEZONE) || defined(HAVE_TM_GMTOFF) struct tm *local = localtime( &t ); #endif #if defined(HAVE_TIMEZONE) //hmm, could make hours & mins static int secs = abs( timezone ); int neg = (timezone > 0) ? 1 : 0; int hours = secs / 3600; int mins = (secs - hours*3600) / 60; // adjust to daylight if ( local->tm_isdst > 0 ) { mDaylight = 1; if ( neg ) { --hours; } else { ++hours; } } else { mDaylight = 0; } #elif defined(HAVE_TM_GMTOFF) int secs = abs( local->tm_gmtoff ); int neg = (local->tm_gmtoff < 0) ? 1 : 0; int hours = secs / 3600; int mins = (secs - hours * 3600) / 60; if ( local->tm_isdst > 0 ) { mDaylight = 1; } else { mDaylight = 0; } #else QDateTime d1 = QDateTime::fromString( asctime( gmtime( &t ) ) ); QDateTime d2 = QDateTime::fromString( asctime( localtime( &t ) ) ); int secs = d1.secsTo( d2 ); int neg = ( secs < 0 ) ? 1 : 0; secs = abs( secs ); int hours = secs / 3600; int mins = (secs - hours * 3600) / 60; // daylight should be already taken care of here #endif /* HAVE_TIMEZONE */ QByteArray ret; QTextStream s( &ret, QIODevice::WriteOnly ); s << ( neg ? '-' : '+' ) << qSetFieldWidth( 2 ) << qSetPadChar( '0' ) << right << hours << mins; //old code: ret.sprintf( "%c%.2d%.2d", (neg) ? '-' : '+', hours, mins ); return ret; } time_t DateFormatter::qdateToTimeT( const QDateTime &dt ) const { QDateTime epoch( QDate( 1970, 1, 1 ), QTime( 00, 00, 00 ) ); time_t t; time( &t ); QDateTime d1 = QDateTime::fromString( asctime( gmtime( &t ) ) ); QDateTime d2 = QDateTime::fromString( asctime( localtime( &t ) ) ); time_t drf = epoch.secsTo( dt ) - d1.secsTo( d2 ); return drf; } QString DateFormatter::fancy( time_t t ) const { KLocale *locale = KGlobal::locale(); if ( t <= 0 ) { return i18nc( "invalid time specified", "unknown" ); } - if ( mTodayOneSecondBeforeMidnight == 0 ) { + if ( mTodayOneSecondBeforeMidnight < time( 0 ) ) { // determine time_t value of today 23:59:59 const QDateTime today( QDate::currentDate(), QTime( 23, 59, 59 ) ); mTodayOneSecondBeforeMidnight = today.toTime_t(); } QDateTime old; old.setTime_t( t ); if ( mTodayOneSecondBeforeMidnight >= t ) { const time_t diff = mTodayOneSecondBeforeMidnight - t; if ( diff < 7 * 24 * 60 * 60 ) { if ( diff < 24 * 60 * 60 ) { return i18n( "Today %1", locale->formatTime( old.time(), true ) ); } if ( diff < 2 * 24 * 60 * 60 ) { return i18n( "Yesterday %1", locale->formatTime( old.time(), true ) ); } for ( int i = 3; i < 8; i++ ) { if ( diff < i * 24 * 60 * 60 ) { return i18nc( "1. weekday, 2. time", "%1 %2" , locale->calendar()->weekDayName( old.date() ) , locale->formatTime( old.time(), true ) ); } } } } return locale->formatDateTime( old ); } QString DateFormatter::localized( time_t t, bool shortFormat, bool includeSecs, const QString &lang ) const { QDateTime tmp; QString ret; KLocale *locale = KGlobal::locale(); tmp.setTime_t( t ); if ( !lang.isEmpty() ) { locale = new KLocale( lang, lang, lang); ret = locale->formatDateTime( tmp, (shortFormat ? KLocale::ShortDate : KLocale::LongDate), includeSecs ); delete locale; } else { ret = locale->formatDateTime( tmp, (shortFormat ? KLocale::ShortDate : KLocale::LongDate), includeSecs ); } return ret; } QString DateFormatter::cTime( time_t t ) const { return QString::fromLatin1( ctime( &t ) ).trimmed(); } QString DateFormatter::isoDate( time_t t ) const { char cstr[64]; strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime( &t ) ); return QString( cstr ); } void DateFormatter::reset() { mTodayOneSecondBeforeMidnight = 0; } QString DateFormatter::formatDate( FormatType ftype, time_t t, const QString &data, bool shortFormat, bool includeSecs ) { DateFormatter f( ftype ); if ( ftype == Custom ) { f.setCustomFormat( data ); } return f.dateString( t, data, shortFormat, includeSecs ); } QString DateFormatter::formatCurrentDate( FormatType ftype, const QString &data, bool shortFormat, bool includeSecs ) { DateFormatter f( ftype ); if ( ftype == Custom ) { f.setCustomFormat( data ); } return f.dateString( time( 0 ), data, shortFormat, includeSecs ); } bool DateFormatter::isDaylight() { if ( mDaylight == -1 ) { time_t ntime = time( 0 ); struct tm *local = localtime( &ntime ); if ( local->tm_isdst > 0 ) { mDaylight = 1; return true; } else { mDaylight = 0; return false; } } else if ( mDaylight != 0 ) { return true; } else { return false; } } diff --git a/kmime/kmime_dateformatter.h b/kmime/kmime_dateformatter.h index 5fb0e475f..1c5b834ea 100644 --- a/kmime/kmime_dateformatter.h +++ b/kmime/kmime_dateformatter.h @@ -1,294 +1,295 @@ /* -*- c++ -*- kmime_dateformatter.h KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the DateFormatter class. @brief Defines the DateFormatter class. @authors the KMime authors (see AUTHORS file) @glossary @anchor RFC2822 @anchor rfc2822 @b RFC @b 2822: RFC that defines the Internet Message Format. @glossary @anchor ISO8601 @anchor iso8601 @b ISO @b 8601: International Standards Organization (ISO) standard that defines the international standard for date and time representations. @glossary @anchor ctime @b ctime: a Unix library call which returns the local time as a human readable ASCII string of the form "Sun Mar 31 02:08:35 2002". */ #ifndef __KMIME_DATEFORMATTER_H__ #define __KMIME_DATEFORMATTER_H__ #include #include #include #include "kmime_export.h" namespace KMime { /** @brief A class for abstracting date formatting. This class deals with different kinds of date display formats. The formats supported include: - @b fancy "Today 02:08:35" - @b ctime as with the @ref ctime function, eg. "Sun Mar 31 02:08:35 2002" - @b localized according to the control center setting, eg. "2002-03-31 02:08" - @b iso according to the @ref ISO8601 standard, eg. "2002-03-31 02:08:35" - @b rfc according to @ref RFC2822 (Section 3.3), eg. "Sun, 31 Mar 2002 02:08:35 -0500" - @b custom "whatever you like" */ class KMIME_EXPORT DateFormatter { public: /** The different types of date formats. */ enum FormatType { CTime, /**< ctime "Sun Mar 31 02:08:35 2002" */ Localized, /**< localized "2002-03-31 02:08" */ Fancy, /**< fancy "Today 02:08:35" */ Iso, /**< iso "2002-03-31 02:08:35" */ Rfc, /**< rfc "Sun, 31 Mar 2002 02:08:35 -0500" */ Custom /**< custom "whatever you like" */ }; /** Constructs a date formatter with a default #FormatType. @param ftype is the default #FormatType to use. */ explicit DateFormatter( FormatType ftype=DateFormatter::Fancy ); /** Destroys the date formatter. */ ~DateFormatter(); /** Returns the #FormatType currently set. @see setFormat(). */ FormatType format() const; /** Sets the date format to @p ftype. @param ftype is the #FormatType. @see format(). */ void setFormat( FormatType ftype ); /** Constructs a formatted date string from time_t @p t. @param t is the time_t to use for formatting. @param lang is the language, only used if #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ QString dateString( time_t t, const QString &lang=QString(), bool shortFormat=true, bool includeSecs=false ) const; /** Constructs a formatted date string from QDateTime @p dtime. @param dtime is the QDateTime to use for formatting. @param lang is the language, only used if #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ QString dateString( const QDateTime &dtime, const QString &lang=QString(), bool shortFormat=true, bool includeSecs=false ) const; /** Sets the custom format for date to string conversions to @p format. This method accepts the same arguments as QDateTime::toString(), but also supports the "Z" expression which is substituted with the @ref RFC2822 (Section 3.3) style numeric timezone (-0500). @param format is a QString containing the custom format. @see QDateTime::toString(), customFormat(). */ void setCustomFormat( const QString &format ); /** Returns the custom format string. @see setCustomFormat(). */ QString customFormat() const; /** Resets the cached current date used for calculating the fancy date. This should be called whenever the current date changed, i.e. on midnight. + @deprecated Can be safely removed. The date change is taken care of internally (as of 4.3). */ void reset(); //static methods /** Convenience function dateString @param ftype is the #FormatType to use. @param t is the time_t to use for formatting. @param data is either the format when #FormatType is Custom, or language when #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ static QString formatDate( DateFormatter::FormatType ftype, time_t t, const QString &data=QString(), bool shortFormat=true, bool includeSecs=false ); /** Convenience function, same as formatDate() but returns the current time formatted. @param ftype is the #FormatType to use. @param data is either the format when #FormatType is Custom, or language when #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ static QString formatCurrentDate( DateFormatter::FormatType ftype, const QString &data=QString(), bool shortFormat=true, bool includeSecs=false ); /** Returns true if the current time is on daylight savings time; else false. */ static bool isDaylight(); protected: /** Returns a QString containing the specified time_t @p t formatted using the #Fancy #FormatType. @param t is the time_t to use for formatting. */ QString fancy( time_t t ) const ; /** Returns a QString containing the specified time_t @p t formatted using the #Localized #FormatType. @param t is the time_t to use for formatting. @param shortFormat if true, create the short version of the date string. @param includeSecs if true, include the seconds field in the date string. @param lang is a QString containing the language to use. */ QString localized( time_t t, bool shortFormat=true, bool includeSecs=false, const QString &lang=QString() ) const; /** Returns a QString containing the specified time_t @p t formatted with the ctime() function. @param t is the time_t to use for formatting. */ QString cTime( time_t t ) const; /** Returns a QString containing the specified time_t @p t in the "%Y-%m-%d %H:%M:%S" #Iso #FormatType. @param t is the time_t to use for formatting. */ QString isoDate( time_t t ) const; /** Returns a QString containing the specified time_t @p t in the #Rfc #FormatType. @param t is the time_t to use for formatting. */ QString rfc2822( time_t t ) const; /** Returns a QString containing the specified time_t @p t formatted with a previously specified custom format. @param t time used for formatting */ QString custom( time_t t ) const; /** Returns a QString that identifies the timezone (eg."-0500") of the specified time_t @p t. @param t time to compute timezone from. */ QByteArray zone( time_t t ) const; /** Converts QDateTime @p dt to a time_t value. @param dt is the QDateTime to be converted. @return the time_t equivalent of the specified QDateTime. */ time_t qdateToTimeT( const QDateTime &dt ) const; private: //@cond PRIVATE FormatType mFormat; mutable time_t mTodayOneSecondBeforeMidnight; mutable QDateTime mUnused; // KDE5: remove QString mCustomFormat; static int mDaylight; //@endcond }; } // namespace KMime #endif /* __KMIME_DATEFORMATTER_H__ */ diff --git a/kresources/kresources.desktop b/kresources/kresources.desktop index a5582825c..8197a727a 100644 --- a/kresources/kresources.desktop +++ b/kresources/kresources.desktop @@ -1,117 +1,119 @@ [Desktop Entry] Icon=view-calendar-day Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kcm_kresources X-KDE-FactoryName=kcm_kresources X-KDE-System-Settings-Parent-Category=advanced-user-settings X-DocPath=kcontrol/kresources/index.html Name=KDE Resources Name[be]=Рэсурсы KDE Name[ca]=Recursos del KDE Name[cs]=Zdroje KDE Name[da]=KDE-ressourcer Name[de]=KDE-Ressourcen Name[el]=Πόροι του KDE Name[es]=Recursos KDE Name[et]=KDE ressursid Name[fr]=Ressources KDE Name[ga]=Acmhainní KDE Name[gl]=Recursos de KDE Name[hne]=केडीई संसाधन Name[hu]=KDE-erőforrások Name[it]=Risorse di KDE Name[ja]=KDE リソース Name[km]=ធនធាន​របស់ KDE Name[lt]=KDE resursai Name[lv]=KDE resursi Name[nb]=KDE-ressurser Name[nds]=KDE Ressourcen Name[nl]=KDE-hulpbronnen Name[nn]=KDE-ressursar Name[pa]=KDE ਸਰੋਤ Name[pl]=Zasoby KDE Name[pt]=Recursos do KDE Name[pt_BR]=Fontes de dados do KDE Name[ro]=Resurse KDE Name[ru]=Источники данных Name[se]=KDE-resurssat +Name[sk]=KDE zdroje Name[sl]=Viri KDE Name[sr]=КДЕ ресурси Name[sr@latin]=KDE resursi Name[sv]=KDE-resurser Name[th]=ทรัพยากร KDE Name[tr]=KDE Kaynakları Name[uk]=Ресурси KDE Name[x-test]=xxKDE Resourcesxx Name[zh_CN]=KDE 资源 Name[zh_TW]=KDE 資源 Comment=Configure KDE Resources Comment[be]=Настаўленні рэсурсаў KDE Comment[ca]=Configura els recursos del KDE Comment[cs]=Nastavení zdrojů KDE Comment[da]=Indstil KDE-ressourcer Comment[de]=KDE-Ressourcen einrichten Comment[el]=Ρύθμιση πόρων του KDE Comment[es]=Configurar recursos de KDE Comment[et]=KDE ressursside seadistamine Comment[fr]=Configurer les ressources KDE Comment[ga]=Cumraigh Acmhainní KDE Comment[gl]=Configurar os recursos de KDE Comment[hne]=केडीई संसाधन मन ल कान्फिगर करव Comment[hu]=A KDE-erőforrások beállítása Comment[it]=Configura le risorse di KDE Comment[ja]=KDE リソースの設定 Comment[km]=កំណត់​រចនាសម្ព័ន្ធ​ធនធាន KDE Comment[lt]=Konfigūruoti KDE resursus Comment[lv]=Konfigurēt KDE resursus Comment[nb]=Sett opp KDE-ressurser Comment[nds]=KDE Ressourcen instellen Comment[nl]=KDE-hulpbronnen instellen Comment[nn]=Set opp KDE-ressursar Comment[pa]=KDE ਸਰੋਤ ਸੰਰਚਨਾ Comment[pl]=Konfiguruj zasoby KDE Comment[pt]=Configurar os Recursos do KDE Comment[pt_BR]=Configurar as fontes de dados do KDE Comment[ro]=Configurare resurse KDE Comment[ru]=Настроить источники данных KDE Comment[se]=Heivet KDE-resurssaid +Comment[sk]=Konfiguruj KDE zdroje Comment[sl]=Nastavi vire KDE Comment[sr]=Подеси ресурсе КДЕ‑а Comment[sr@latin]=Podesi resurse KDE‑a Comment[sv]=Anpassa KDE-resurser Comment[th]=ปรับแต่งทรัพยากร KDE Comment[tr]=KDE Kaynaklarını Yapılandır Comment[uk]=Налаштувати ресурси KDE Comment[x-test]=xxConfigure KDE Resourcesxx Comment[zh_CN]=配置 KDE 资源 Comment[zh_TW]=設定 KDE 資源 X-KDE-Keywords=resources,konnector resource,contact resource,calendar resource,notes resource,alarm resource,imap X-KDE-Keywords[ca]=recursos,recurs del konnector,recurs del contacte,recurs del calendari, recurs de les notes,recurs d'alarma,imap X-KDE-Keywords[de]=Ressourcen,Konnector-Ressource,Kontakt-Ressource,Kalender-Ressource,Notizen-Ressource,Alarm-Ressource,Imap X-KDE-Keywords[el]=πόροι,πόροι του konnector,πόρος επαφής,πόρος ημερολογίου,πόρος σημειώσεων,πόρος ειδοποίησης,imap X-KDE-Keywords[es]=recursos,recursos de konnector,recursos de contacto,recursos de calendario,recursos de notas,recurso de alarma,imap X-KDE-Keywords[et]=ressursid,konnektori ressurss,kontakti ressurss,kalendri ressurss,märkmete ressurss,häire ressurss,imap X-KDE-Keywords[fr]=ressources,konnector, ressource contact calendrier, agenda, notes resource,alarme, imap X-KDE-Keywords[ga]=acmhainní,acmhainn konnector,acmhainn teagmhála,acmhainn fhéilire,acmhainn nótaí,acmhainn aláraim,imap X-KDE-Keywords[gl]=recursos,recurso de konnector,recurso de contactos,recurso de axenda, recurso de notas, recurso de alarma, imap X-KDE-Keywords[hu]=erőforrások,Konnector-erőforrás,névjegy,naptár,feljegyzés,emlékeztető,imap X-KDE-Keywords[it]=risorse,risorsa di konnector,risorsa dei contatti,risorsa di calendario,risorse di note,resource,konnector resource,contact resource,calendar resource,notes resource,alarm resource,imap X-KDE-Keywords[ja]=リソース,konnector resource, コネクターリソース,コンタクトリソース,カレンダーリソース, メモリソース,アラームリソースimap X-KDE-Keywords[lv]=resursi, konnector resurss, kontaktu resurss, kalendāra resurss, piezīmju resurss, modinātāja resurss, imap X-KDE-Keywords[nb]=ressurser,konnector ressurs,kontaktressurs,kalenderressurs,notes-ressurs,varslingsressurs,imap X-KDE-Keywords[nds]=Ressourcen,konnector-Ressource,Kontakt-Ressource,Kalenner-Ressource,Notiz-Ressource,Alarm-Ressource,IMAP X-KDE-Keywords[nl]=hulpbronnen,konnector-hulpbron,contactpersoonhulpbron,agendahulpbron,notitiehulpbron,herinneringhulpbron,imap X-KDE-Keywords[pl]=zasoby,zasoby konnectora,zasoby kontaktów,zasoby kalendarza,zasoby notatek,zasoby alarmu,imap X-KDE-Keywords[pt]=recursos,recurso do konector,recurso de contactos,recurso de calendário,recurso de notas,recurso de alarme,imap X-KDE-Keywords[pt_BR]=recursos,recurso do konnector,recurso de contatos,recurso de calendário,recurso de notas,recurso de alarme,imap X-KDE-Keywords[sr]=resources,konnector resource,contact resource,calendar resource,notes resource,alarm resource,imap,ресурс,конектор,контакт,календар,белешке,забелешке,аларм X-KDE-Keywords[sr@latin]=resources,konnector resource,contact resource,calendar resource,notes resource,alarm resource,imap,resurs,konektor,kontakt,kalendar,beleške,zabeleške,alarm X-KDE-Keywords[sv]=resurser,konnector-resurs,kontaktresurs,kalenderresurs,anteckningsresurs,alarmresurs,IMAP X-KDE-Keywords[uk]=resources,konnector resource,contact resource,calendar resource,notes resource,alarm resource,imap,ресурс календаря,ресурси X-KDE-Keywords[x-test]=xxresources,konnector resource,contact resource,calendar resource,notes resource,alarm resource,imapxx X-KDE-Keywords[zh_CN]=resources,konnector resource,contact resource,calendar resource,notes resource,alarm resource,imap,资源,konnector 资源,联系人资源,日历资源,备忘资源,提醒资源 diff --git a/kresources/kresources_manager.desktop b/kresources/kresources_manager.desktop index a63f5b387..cb63e2f2c 100644 --- a/kresources/kresources_manager.desktop +++ b/kresources/kresources_manager.desktop @@ -1,84 +1,86 @@ [Desktop Entry] Name=KResources Manager Name[ca]=Gestor del KResources Name[cs]=KResource správce Name[da]=KResources-håndtering Name[de]=Ressourcenverwaltung Name[el]=Διαχειριστής KResources Name[es]=Gestor de KResources Name[et]=KDE ressursside haldur Name[fr]=Gestionnaire de ressources Name[ga]=Bainisteoir KResources Name[gl]=Xestor de KResources Name[hne]=केरिसोर्स प्रबंधक Name[hu]=KResources-kezelő Name[it]=Gestore di KResources Name[ja]=KDE リソースマネージャ Name[km]=កម្មវិធី​គ្រប់គ្រង KResources Name[lt]=KResursų tvarkytuvė Name[lv]=KResources pārvaldnieks Name[nb]=KResources-styring Name[nds]=KResources-Pleger Name[nl]=KResource-beheerder Name[nn]=KResources-handsaming Name[pa]=ਕੇ-ਰੀਸੋਰਸ ਮੈਨੇਜਰ Name[pl]=Menadżer zasobów KDE (KResource) Name[pt]=Gestor do KResources Name[pt_BR]=Gerenciador do KResource Name[ro]=Gestionar KResurse Name[ru]=Управление источниками данных Name[se]=KResources-gieđahalli +Name[sk]=KResources manažér Name[sl]=Upravnik KResources Name[sr]=Менаџер к‑ресурса Name[sr@latin]=Menadžer k‑resursa Name[sv]=Resurshantering Name[th]=ตัวจัดการทรัพยากร Name[tr]=KResources Yöneticisi Name[uk]=Менеджер KResource-ів Name[x-test]=xxKResources Managerxx Name[zh_CN]=KResource 管理器 Name[zh_TW]=KResources 管理者 Type=ServiceType X-KDE-ServiceType=KResources/Manager Comment=KResources Manager Comment[ca]=Gestor del KResources Comment[cs]=KResource správce Comment[da]=KResources-håndtering Comment[de]=Ressourcenverwaltung Comment[el]=Διαχειριστής KResources Comment[es]=Gestor de KResources Comment[et]=KDE ressursside haldur Comment[fr]=Gestionnaire de ressources Comment[ga]=Bainisteoir KResources Comment[gl]=Xestor de KResources Comment[hne]=केरिसोर्स प्रबंधक Comment[hu]=KResources-kezelő Comment[it]=Gestore di KResources Comment[ja]=KDE リソースマネージャ Comment[km]=កម្មវិធី​គ្រប់គ្រង KResources Comment[lt]=KResursų tvarkytuvė Comment[lv]=KResources pārvaldnieks Comment[nb]=KResources-styring Comment[nds]=KDE-Ressourcenpleger Comment[nl]=KResource-beheerder Comment[nn]=KResource-handsaming Comment[pa]=ਕੇ-ਰੀਸੋਰਸ ਮੈਨੇਜਰ Comment[pl]=Menadżer zasobów KDE (KResource) Comment[pt]=Gestor do KResources Comment[pt_BR]=Gerenciador do KResource Comment[ro]=Gestionar KResurse Comment[ru]=Управление источниками данных Comment[se]=KResources-gieđahalli +Comment[sk]=KResources manažér Comment[sl]=Upravnik KResources Comment[sr]=Менаџер к‑ресурса Comment[sr@latin]=Menadžer k‑resursa Comment[sv]=Resurshantering Comment[th]=ตัวจัดการทรัพยากร K Comment[tr]=KResources Yöneticisi Comment[uk]=Менеджер KResource-ів Comment[x-test]=xxKResources Managerxx Comment[zh_CN]=KResource 管理器 Comment[zh_TW]=KResources 管理者 [PropertyDef::X-KDE-ResourceFamily] Type=QString diff --git a/kresources/kresources_plugin.desktop b/kresources/kresources_plugin.desktop index 53d51730d..4308694b3 100644 --- a/kresources/kresources_plugin.desktop +++ b/kresources/kresources_plugin.desktop @@ -1,86 +1,88 @@ [Desktop Entry] Name=KResources Plugin Name[ca]=Connector pel KResources Name[cs]=Modul rámce KResource Name[da]=KResources-plugin Name[de]=KRessourcen-Modul Name[el]=Πρόσθετο KResources Name[es]=Complemento de KResources Name[et]=KDE ressursside plugin Name[fr]=Module externe de gestion de ressources Name[ga]=Breiseán KResources Name[gl]=Extensión de KResources Name[hne]=केरिसोर्स प्लगइन Name[hu]=KResources-modul Name[it]=Plugin di KResources Name[ja]=KDE リソースプラグイン Name[km]=កម្មវិធី​ជំនួយ​របស់ KResources Name[lt]=KResursų priedas Name[lv]=KResources spraudnis Name[nb]=Programtillegg for KResources Name[nds]=KResources-Moduul Name[nl]=KResource-plugin Name[nn]=Programtillegg for KResource-rammeverket Name[pa]=ਕੇਸਰੋਤ ਪਲੱਗਇਨ Name[pl]=Wtyczka KResource Name[pt]='Plugin' do KResources Name[pt_BR]=Plug-in do KResource Name[ro]=Modul KResurse Name[ru]=Модуль KResources Name[se]=KResources-lassemodula +Name[sk]=KResources zásuvný modul Name[sl]=Vstavek KResources Name[sr]=Прикључак радног оквира к‑ресурса Name[sr@latin]=Priključak radnog okvira k‑resursa Name[sv]=Insticksprogram för resurser Name[th]=โปรแกรมเสริมการจัดการทรัพยากร K Name[tr]=KResources Eklentisi Name[uk]=Втулок KResourc-ів Name[x-test]=xxKResources Pluginxx Name[zh_CN]=KResource 插件 Name[zh_TW]=KResources 外掛程式 Type=ServiceType X-KDE-ServiceType=KResources/Plugin Comment=KResources Framework Plugin Comment[ca]=Connector del marc de treball pel KResources Comment[cs]=Modul rámce KResource Comment[da]=KResource-strukturplugin Comment[de]=Modul für KRessourcen-Grundgerüst Comment[el]=Πρόσθετο πλαισίου εργασίας του KResource Comment[es]=Complemento de estructura de KResources Comment[et]=KDE ressursside raamistiku plugin Comment[fr]=Module externe d'infrastructure de ressources Comment[ga]=Breiseán Creatlaí KResources Comment[gl]=Extensión da infraestrutura KResource Comment[hne]=केरिसोर्स फ्रेमवर्क प्लगइन Comment[hu]=Modul a KResources keretrendszerhez Comment[it]=Plugin di infrastruttura di KResources Comment[ja]=KDE リソースフレームワークのプラグイン Comment[km]=កម្មវិធី​ជំនួយ​គ្រោងការ​របស់ KResources Comment[lt]=KResursų aplinkos priedas Comment[lv]=KResource karkasa spraudnis Comment[nb]=Programtillegg for KResources-rammeverket Comment[nds]=Rahmenwark-Moduul för KResources Comment[nl]=KResource-raamwerkplugin Comment[nn]=Programtillegg for KResource-rammeverket Comment[pa]=ਕੇਸਰੋਤ ਫਰੇਮਵਰਕ ਪਲੱਗਇਨ Comment[pl]=Wtyczka modułu KResource Comment[pt]='Plugin' da Plataforma do KResources Comment[pt_BR]=Plug-in do KResource Comment[ro]=Modul de arhitectură KResurse Comment[ru]=Модуль инфраструктуры KResource +Comment[sk]=KResources Framework zásuvný modul Comment[sl]=Vstavek za ogrodje KResources Comment[sr]=Прикључак радног оквира к‑ресурса Comment[sr@latin]=Priključak radnog okvira k‑resursa Comment[sv]=Insticksprogram för resursramverk Comment[th]=โปรแกรมเสริมเฟรมเวิร์กการจัดการทรัพยากร K Comment[tr]=KResources Çalışma Alanı Eklentisi Comment[uk]=Втулок платформи KResource Comment[x-test]=xxKResources Framework Pluginxx Comment[zh_CN]=KResource 框架插件 Comment[zh_TW]=KResources 框架外掛程式 [PropertyDef::X-KDE-ResourceFamily] Type=QString [PropertyDef::X-KDE-ResourceType] Type=QString diff --git a/mailtransport/mailtransport.kcfg b/mailtransport/mailtransport.kcfg index 248ddf9fa..c1a71f331 100644 --- a/mailtransport/mailtransport.kcfg +++ b/mailtransport/mailtransport.kcfg @@ -1,115 +1,115 @@ 0 The name that will be used when referring to this server. i18n("Unnamed") SMTP The domain name or numerical address of the SMTP server. The port number that the SMTP server is listening on. The default port is 25. 25 The user name to send to the server for authorization. A command to run locally, prior to sending email. This can be used to set up SSH tunnels, for example. Leave it empty if no command should be run. Check this option if your SMTP server requires authentication before accepting mail. This is known as 'Authenticated SMTP' or simply ASMTP. false Check this option to have your password stored. - \nIf KWallet is available the password will be stored there which is considered safe.\n + If KWallet is available the password will be stored there which is considered safe. However, if KWallet is not available, the password will be stored in the configuration file. The password is stored in an obfuscated format, but should not be considered secure from decryption efforts if access to the configuration file is obtained. false PLAIN Check this option to use a custom hostname when identifying to the mail server. This is useful when your system's hostname may not be set correctly or to mask your system's true hostname. false Enter the hostname that should be used when identifying to the server.